BSD 3 development
authorBill Joy <wnj@ucbvax.Berkeley.EDU>
Fri, 4 Jan 1980 10:38:10 +0000 (02:38 -0800)
committerBill Joy <wnj@ucbvax.Berkeley.EDU>
Fri, 4 Jan 1980 10:38:10 +0000 (02:38 -0800)
Work on file usr/src/cmd/ex/:rofix
Work on file usr/src/cmd/ex/READ_ME
Work on file usr/src/cmd/ex/errs
Work on file usr/src/cmd/ex/ex.c
Work on file usr/src/cmd/ex/ex.h
Work on file usr/src/cmd/ex/ex_addr.c
Work on file usr/src/cmd/ex/ex_argv.h
Work on file usr/src/cmd/ex/ex_cmds.c
Work on file usr/src/cmd/ex/ex_cmds2.c
Work on file usr/src/cmd/ex/ex_cmdsub.c
Work on file usr/src/cmd/ex/ex_data.c
Work on file usr/src/cmd/ex/ex_get.c
Work on file usr/src/cmd/ex/ex_io.c
Work on file usr/src/cmd/ex/ex_put.c
Work on file usr/src/cmd/ex/ex_re.c
Work on file usr/src/cmd/ex/ex_re.h
Work on file usr/src/cmd/ex/ex_set.c
Work on file usr/src/cmd/ex/ex_subr.c
Work on file usr/src/cmd/ex/ex_temp.c
Work on file usr/src/cmd/ex/ex_temp.h
Work on file usr/src/cmd/ex/ex_tty.c
Work on file usr/src/cmd/ex/ex_tty.h
Work on file usr/src/cmd/ex/ex_tune.h
Work on file usr/src/cmd/ex/ex_v.c
Work on file usr/src/cmd/ex/ex_vadj.c
Work on file usr/src/cmd/ex/ex_vars.h
Work on file usr/src/cmd/ex/ex_vget.c
Work on file usr/src/cmd/ex/ex_vis.h
Work on file usr/src/cmd/ex/ex_vmain.c
Work on file usr/src/cmd/ex/ex_voperate.c
Work on file usr/src/cmd/ex/ex_vops.c
Work on file usr/src/cmd/ex/ex_vops2.c
Work on file usr/src/cmd/ex/ex_vops3.c
Work on file usr/src/cmd/ex/ex_vput.c
Work on file usr/src/cmd/ex/ex_vwind.c
Work on file usr/src/cmd/ex/expreserve.c
Work on file usr/src/cmd/ex/makefile
Work on file usr/src/cmd/ex/makeoptions
Work on file usr/src/cmd/ex/popen.c
Work on file usr/src/cmd/ex/printf.c

Synthesized-from: 3bsd

40 files changed:
usr/src/cmd/ex/:rofix [new file with mode: 0644]
usr/src/cmd/ex/READ_ME [new file with mode: 0644]
usr/src/cmd/ex/errs [new file with mode: 0644]
usr/src/cmd/ex/ex.c [new file with mode: 0644]
usr/src/cmd/ex/ex.h [new file with mode: 0644]
usr/src/cmd/ex/ex_addr.c [new file with mode: 0644]
usr/src/cmd/ex/ex_argv.h [new file with mode: 0644]
usr/src/cmd/ex/ex_cmds.c [new file with mode: 0644]
usr/src/cmd/ex/ex_cmds2.c [new file with mode: 0644]
usr/src/cmd/ex/ex_cmdsub.c [new file with mode: 0644]
usr/src/cmd/ex/ex_data.c [new file with mode: 0644]
usr/src/cmd/ex/ex_get.c [new file with mode: 0644]
usr/src/cmd/ex/ex_io.c [new file with mode: 0644]
usr/src/cmd/ex/ex_put.c [new file with mode: 0644]
usr/src/cmd/ex/ex_re.c [new file with mode: 0644]
usr/src/cmd/ex/ex_re.h [new file with mode: 0644]
usr/src/cmd/ex/ex_set.c [new file with mode: 0644]
usr/src/cmd/ex/ex_subr.c [new file with mode: 0644]
usr/src/cmd/ex/ex_temp.c [new file with mode: 0644]
usr/src/cmd/ex/ex_temp.h [new file with mode: 0644]
usr/src/cmd/ex/ex_tty.c [new file with mode: 0644]
usr/src/cmd/ex/ex_tty.h [new file with mode: 0644]
usr/src/cmd/ex/ex_tune.h [new file with mode: 0644]
usr/src/cmd/ex/ex_v.c [new file with mode: 0644]
usr/src/cmd/ex/ex_vadj.c [new file with mode: 0644]
usr/src/cmd/ex/ex_vars.h [new file with mode: 0644]
usr/src/cmd/ex/ex_vget.c [new file with mode: 0644]
usr/src/cmd/ex/ex_vis.h [new file with mode: 0644]
usr/src/cmd/ex/ex_vmain.c [new file with mode: 0644]
usr/src/cmd/ex/ex_voperate.c [new file with mode: 0644]
usr/src/cmd/ex/ex_vops.c [new file with mode: 0644]
usr/src/cmd/ex/ex_vops2.c [new file with mode: 0644]
usr/src/cmd/ex/ex_vops3.c [new file with mode: 0644]
usr/src/cmd/ex/ex_vput.c [new file with mode: 0644]
usr/src/cmd/ex/ex_vwind.c [new file with mode: 0644]
usr/src/cmd/ex/expreserve.c [new file with mode: 0644]
usr/src/cmd/ex/makefile [new file with mode: 0644]
usr/src/cmd/ex/makeoptions [new file with mode: 0755]
usr/src/cmd/ex/popen.c [new file with mode: 0644]
usr/src/cmd/ex/printf.c [new file with mode: 0644]

diff --git a/usr/src/cmd/ex/:rofix b/usr/src/cmd/ex/:rofix
new file mode 100644 (file)
index 0000000..cd5fa24
--- /dev/null
@@ -0,0 +1,3 @@
+g/^[   ]*\.data/s//.text/
+w
+q
diff --git a/usr/src/cmd/ex/READ_ME b/usr/src/cmd/ex/READ_ME
new file mode 100644 (file)
index 0000000..a6141c2
--- /dev/null
@@ -0,0 +1,43 @@
+
+This is version 3.1 of the editor. It is too large to fit on a pdp-11
+unless you have overlay code. (Such code is expected to be available
+for v7 unix soon.)
+
+Version 2.9 corresponds to version 3.1 without the enhancements in 3.1.
+There is no reason to use 2.9 unless you have a pdp-11 that does not have
+overlay software, since 3.1 contains all the bug fixes and some new features.
+
+Special installation notes for this version.
+1) If on a V6 system using -lretro, be sure to remove the line in
+   /usr/include/retrofit/sgtty.h that defines CBREAK. If you have
+   added a line defining TIOCSETN or TIOCGETC these should be removed.
+2) The include file varargs.h should be installed, as the new printf needs it.
+3) The include file local/uparm.h should be installed, as ex_tune.h needs it.
+   The contents of this include file can be modified if you wish to place
+   the editor in a nonstandard location.
+4) Be sure not to use the -t1 compiler (which puts switches in I space and
+   hence makes larger I segments. This will causes the editor not to fit
+   in 64K on an 11.
+5) Use the -t0 compiler which has a large enough symbol table. (V6 only)
+6) Be sure to use the new termlib that goes with this version of the editor.
+7) Be sure to use the new termcap.
+8) Make sure the programs setenv and printenv are installed, and that setenv
+   is able to write /etc/htmp. (V6 only)
+
+Conditional compilation flags:
+       -DTRACE         for debugging (wont then fit on an 11)
+       -DV6            for version 6, using raw (v7 uses cbreak)
+       -DVFORK         for UCB Vax/Unix with the vfork system call.
+       -DCHDIR         compile in undocumented old chdir (cd) command
+       -DLISP          compile in lisp hacks
+       -DUCVISUAL      compile in code to handle \ escapes for visual on
+                       upper case only terminals.  gross.
+
+Ex means to avoid stdio like the plague. If any of stdio other than the
+ctype.h functions or str* get pulled in, it is a mistake.
+
+Ex is very large, but should fit (barely) on an 11/70.  There are only
+a few bytes of room left in version 2.9 unless you take out some of
+CHDIR, LISP, or UCVISUAL. This assumes the new termlib (which knows
+about the tc= capability, 1024 byte entries, and @ cancellation of
+capabilities); and that -t1 is NOT used for compilation.
diff --git a/usr/src/cmd/ex/errs b/usr/src/cmd/ex/errs
new file mode 100644 (file)
index 0000000..1c4618b
--- /dev/null
@@ -0,0 +1,9 @@
+cc -E -DTABS=8 -DLISPCODE -DCHDIR -DUCVISUAL -DMACROS -DVFORK -DVMUNIX -O ex_cmds.c | /usr/ucb/xstr -c -
+cc -DTABS=8 -DLISPCODE -DCHDIR -DUCVISUAL -DMACROS -DVFORK -DVMUNIX -O -c x.c 
+mv x.o ex_cmds.o
+/usr/ucb/xstr
+cc -c -S xs.c
+ed - <:rofix xs.s
+as -o strings.o xs.s
+rm xs.s
+cc -i ex.o ex_addr.o ex_cmds.o ex_cmds2.o ex_cmdsub.o ex_data.o ex_get.o  ex_io.o ex_put.o ex_re.o ex_set.o ex_subr.o ex_temp.o ex_tty.o  ex_v.o ex_vadj.o ex_vget.o ex_vmain.o ex_voperate.o  ex_vops.o ex_vops2.o ex_vops3.o ex_vput.o ex_vwind.o  printf.o strings.o -ltermlib
diff --git a/usr/src/cmd/ex/ex.c b/usr/src/cmd/ex/ex.c
new file mode 100644 (file)
index 0000000..e74b5c0
--- /dev/null
@@ -0,0 +1,462 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_argv.h"
+#include "ex_temp.h"
+#include "ex_tty.h"
+
+#ifdef TRACE
+char   tttrace[]       = { '/','d','e','v','/','t','t','y','x','x',0 };
+#endif
+
+/*
+ * The code for ex is divided as follows:
+ *
+ * ex.c                        Entry point and routines handling interrupt, hangup
+ *                     signals; initialization code.
+ *
+ * ex_addr.c           Address parsing routines for command mode decoding.
+ *                     Routines to set and check address ranges on commands.
+ *
+ * ex_cmds.c           Command mode command decoding.
+ *
+ * ex_cmds2.c          Subroutines for command decoding and processing of
+ *                     file names in the argument list.  Routines to print
+ *                     messages and reset state when errors occur.
+ *
+ * ex_cmdsub.c         Subroutines which implement command mode functions
+ *                     such as append, delete, join.
+ *
+ * ex_data.c           Initialization of options.
+ *
+ * ex_get.c            Command mode input routines.
+ *
+ * ex_io.c             General input/output processing: file i/o, unix
+ *                     escapes, filtering, source commands, preserving
+ *                     and recovering.
+ *
+ * ex_put.c            Terminal driving and optimizing routines for low-level
+ *                     output (cursor-positioning); output line formatting
+ *                     routines.
+ *
+ * ex_re.c             Global commands, substitute, regular expression
+ *                     compilation and execution.
+ *
+ * ex_set.c            The set command.
+ *
+ * ex_subr.c           Loads of miscellaneous subroutines.
+ *
+ * ex_temp.c           Editor buffer routines for main buffer and also
+ *                     for named buffers (Q registers if you will.)
+ *
+ * ex_tty.c            Terminal dependent initializations from termcap
+ *                     data base, grabbing of tty modes (at beginning
+ *                     and after escapes).
+ *
+ * ex_v*.c             Visual/open mode routines... see ex_v.c for a
+ *                     guide to the overall organization.
+ */
+
+/*
+ * Main procedure.  Process arguments and then
+ * transfer control to the main command processing loop
+ * in the routine commands.  We are entered as either "ex", "edit" or "vi"
+ * and the distinction is made here.  Actually, we are "vi" if
+ * there is a 'v' in our name, and "edit" if there is a 'd' in our
+ * name.  For edit we just diddle options; for vi we actually
+ * force an early visual command, setting the external initev so
+ * the q command in visual doesn't give command mode.
+ */
+main(ac, av)
+       register int ac;
+       register char *av[];
+{
+#ifndef VMUNIX
+       char *erpath = EXSTRINGS;
+#endif
+       register char *cp;
+       register int c;
+       bool recov = 0;
+       bool ivis;
+       bool itag = 0;
+       bool fast = 0;
+#ifdef TRACE
+       register char *tracef;
+#endif
+
+       /*
+        * Immediately grab the tty modes so that we wont
+        * get messed up if an interrupt comes in quickly.
+        */
+       gTTY(1);
+       normf = tty.sg_flags;
+       ppid = getpid();
+       /*
+        * Defend against d's, v's, and a's in directories of
+        * path leading to our true name.
+        */
+       av[0] = tailpath(av[0]);
+       ivis = any('v', av[0]);
+
+       /*
+        * For debugging take files out of . if name is a.out.
+        * If a 'd' in our name, then set options for edit.
+        */
+#ifndef VMUNIX
+       if (av[0][0] == 'a')
+               erpath = tailpath(erpath);
+#endif
+       if (ivis) {
+#ifdef notdef
+               options[BEAUTIFY].odefault = value(BEAUTIFY) = 1;
+#endif
+       } else if (any('d', av[0])) {
+               value(OPEN) = 0;
+               value(REPORT) = 1;
+               value(MAGIC) = 0;
+       }
+
+       /*
+        * Open the error message file.
+        */
+       draino();
+#ifndef VMUNIX
+       erfile = open(erpath+4, 0);
+       if (erfile < 0) {
+               erfile = open(erpath, 0);
+       }
+#endif
+       pstop();
+
+       /*
+        * Initialize interrupt handling.
+        */
+       oldhup = signal(SIGHUP, SIG_IGN);
+       if (oldhup == SIG_DFL)
+               signal(SIGHUP, onhup);
+       oldquit = signal(SIGQUIT, SIG_IGN);
+       ruptible = signal(SIGINT, SIG_IGN) == SIG_DFL;
+       if (signal(SIGTERM, SIG_IGN) == SIG_DFL)
+               signal(SIGTERM, onhup);
+
+       /*
+        * Initialize end of core pointers.
+        * Normally we avoid breaking back to fendcore after each
+        * file since this can be expensive (much core-core copying).
+        * If your system can scatter load processes you could do
+        * this as ed does, saving a little core, but it will probably
+        * not often make much difference.
+        */
+       fendcore = (line *) sbrk(0);
+       endcore = fendcore - 2;
+
+       /*
+        * Process flag arguments.
+        */
+       ac--, av++;
+       while (ac && av[0][0] == '-') {
+               c = av[0][1];
+               if (c == 0) {
+                       hush = 1;
+                       value(AUTOPRINT) = 0;
+                       fast++;
+               } else switch (c) {
+
+#ifdef TRACE
+               case 'T':
+                       if (av[0][2] == 0)
+                               tracef = "trace";
+                       else {
+                               tracef = tttrace;
+                               tracef[8] = av[0][2];
+                               if (tracef[8])
+                                       tracef[9] = av[0][3];
+                               else
+                                       tracef[9] = 0;
+                       }
+                       trace = fopen(tracef, "w");
+                       if (trace == NULL)
+                               printf("Trace create error\n");
+                       setbuf(trace, tracbuf);
+                       break;
+
+#endif
+
+#ifdef LISPCODE
+               case 'l':
+                       value(LISP) = 1;
+                       value(SHOWMATCH) = 1;
+                       break;
+#endif
+
+               case 'r':
+                       recov++;
+                       break;
+
+               case 't':
+                       if (ac > 1 && av[1][0] != '-') {
+                               ac--, av++;
+                               itag = 1;
+                               /* BUG: should check for too long tag. */
+                               CP(lasttag, av[0]);
+                       }
+                       break;
+
+               case 'v':
+                       ivis = 1;
+                       break;
+
+               case 'w':
+                       defwind = 0;
+                       if (av[0][2] == 0) defwind = 3;
+                       else for (cp = &av[0][2]; isdigit(*cp); cp++)
+                               defwind = 10*defwind + *cp - '0';
+                       break;
+
+               default:
+                       smerror("Unknown option %s\n", av[0]);
+                       break;
+               }
+               ac--, av++;
+       }
+       if (ac && av[0][0] == '+') {
+               firstpat = &av[0][1];
+               ac--, av++;
+       }
+
+       /*
+        * If we are doing a recover and no filename
+        * was given, then execute an exrecover command with
+        * the -r option to type out the list of saved file names.
+        * Otherwise set the remembered file name to the first argument
+        * file name so the "recover" initial command will find it.
+        */
+       if (recov) {
+               if (ac == 0) {
+                       ppid = 0;
+                       setrupt();
+                       execl(EXRECOVER, "exrecover", "-r", 0);
+                       filioerr(EXRECOVER);
+                       exit(1);
+               }
+               CP(savedfile, *av++), ac--;
+       }
+
+       /*
+        * Initialize the argument list.
+        */
+       argv0 = av;
+       argc0 = ac;
+       args0 = av[0];
+       erewind();
+
+       /*
+        * Initialize a temporary file (buffer) and
+        * set up terminal environment.  Read user startup commands.
+        */
+       init();
+       if (setexit() == 0) {
+               setrupt();
+               intty = isatty(0);
+               value(PROMPT) = intty;
+               if (fast || !intty)
+                       setterm("dumb");
+               else {
+                       gettmode();
+                       if ((cp = getenv("TERM")) != 0)
+                               setterm(cp);
+               }
+       }
+       if (setexit() == 0 && !fast && intty)
+               if (globp = getenv("EXINIT"))
+                       commands(1,1);
+               else if ((cp = getenv("HOME")) != 0)
+                       source(strcat(strcpy(genbuf, cp), "/.exrc"), 1);
+
+       /*
+        * Initial processing.  Handle tag, recover, and file argument
+        * implied next commands.  If going in as 'vi', then don't do
+        * anything, just set initev so we will do it later (from within
+        * visual).
+        */
+       if (setexit() == 0) {
+               if (recov)
+                       globp = "recover";
+               else if (itag)
+                       globp = ivis ? "tag" : "tag|p";
+               else if (argc)
+                       globp = "next";
+               if (ivis)
+                       initev = globp;
+               else if (globp) {
+                       inglobal = 1;
+                       commands(1, 1);
+                       inglobal = 0;
+               }
+       }
+
+       /*
+        * Vi command... go into visual.
+        * Strange... everything in vi usually happens
+        * before we ever "start".
+        */
+       if (ivis) {
+               /*
+                * Don't have to be upward compatible with stupidity
+                * of starting editing at line $.
+                */
+               if (dol > zero)
+                       dot = one;
+               globp = "visual";
+               if (setexit() == 0)
+                       commands(1, 1);
+       }
+
+       /*
+        * Clear out trash in state accumulated by startup,
+        * and then do the main command loop for a normal edit.
+        * If you quit out of a 'vi' command by doing Q or ^\,
+        * you also fall through to here.
+        */
+       ungetchar(0);
+       globp = 0;
+       initev = 0;
+       setlastchar('\n');
+       setexit();
+       commands(0, 0);
+       cleanup(1);
+       exit(0);
+}
+
+/*
+ * Initialization, before editing a new file.
+ * Main thing here is to get a new buffer (in fileinit),
+ * rest is peripheral state resetting.
+ */
+init()
+{
+       register int i;
+
+       fileinit();
+       dot = zero = truedol = unddol = dol = fendcore;
+       one = zero+1;
+       undkind = UNDNONE;
+       chng = 0;
+       edited = 0;
+       for (i = 0; i <= 'z'-'a'+1; i++)
+               names[i] = 1;
+       anymarks = 0;
+}
+
+/*
+ * When a hangup occurs our actions are similar to a preserve
+ * command.  If the buffer has not been [Modified], then we do
+ * nothing but remove the temporary files and exit.
+ * Otherwise, we sync the temp file and then attempt a preserve.
+ * If the preserve succeeds, we unlink our temp files.
+ * If the preserve fails, we leave the temp files as they are
+ * as they are a backup even without preservation if they
+ * are not removed.
+ */
+onhup()
+{
+
+       if (chng == 0) {
+               cleanup(1);
+               exit(0);
+       }
+       if (setexit() == 0) {
+               if (preserve()) {
+                       cleanup(1);
+                       exit(0);
+               }
+       }
+       exit(1);
+}
+
+/*
+ * An interrupt occurred.  Drain any output which
+ * is still in the output buffering pipeline.
+ * Catch interrupts again.  Unless we are in visual
+ * reset the output state (out of -nl mode, e.g).
+ * Then like a normal error (with the \n before Interrupt
+ * suppressed in visual mode).
+ */
+onintr()
+{
+
+#ifndef CBREAK
+       signal(SIGINT, onintr);
+#else
+       signal(SIGINT, inopen ? vintr : onintr);
+#endif
+       draino();
+       if (!inopen) {
+               pstop();
+               setlastchar('\n');
+#ifdef CBREAK
+       }
+#else
+       } else
+               vraw();
+#endif
+       error("\nInterrupt" + inopen);
+}
+
+/*
+ * If we are interruptible, enable interrupts again.
+ * In some critical sections we turn interrupts off,
+ * but not very often.
+ */
+setrupt()
+{
+
+       if (ruptible)
+#ifndef CBREAK
+               signal(SIGINT, onintr);
+#else
+               signal(SIGINT, inopen ? vintr : onintr);
+#endif
+}
+
+preserve()
+{
+
+       synctmp();
+       pid = fork();
+       if (pid < 0)
+               return (0);
+       if (pid == 0) {
+               close(0);
+               dup(tfile);
+               execl(EXPRESERVE, "expreserve", (char *) 0);
+               exit(1);
+       }
+       waitfor();
+       if (rpid == pid && status == 0)
+               return (1);
+       return (0);
+}
+
+#ifndef V6
+exit(i)
+       int i;
+{
+
+       _exit(i);
+}
+#endif
+
+/*
+ * Return last component of unix path name p.
+ */
+char *
+tailpath(p)
+register char *p;
+{
+       register char *r;
+
+       for (r=p; *p; p++)
+               if (*p == '/')
+                       r = p+1;
+       return(r);
+}
diff --git a/usr/src/cmd/ex/ex.h b/usr/src/cmd/ex/ex.h
new file mode 100644 (file)
index 0000000..9493541
--- /dev/null
@@ -0,0 +1,351 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#ifdef V6
+#include <retrofit.h>
+#endif
+
+/*
+ * Ex version 3.1
+ *
+ * Mark Horton, UC Berkeley
+ * Bill Joy, UC Berkeley
+ * November 1979
+ *
+ * This file contains most of the declarations common to a large number
+ * of routines.  The file ex_vis.h contains declarations
+ * which are used only inside the screen editor.
+ * The file ex_tune.h contains parameters which can be diddled per installation.
+ *
+ * The declarations relating to the argument list, regular expressions,
+ * the temporary file data structure used by the editor
+ * and the data describing terminals are each fairly substantial and
+ * are kept in the files ex_{argv,re,temp,tty}.h which
+ * we #include separately.
+ *
+ * If you are going to dig into ex, you should look at the outline of the
+ * distribution of the code into files at the beginning of ex.c and ex_v.c.
+ * Code which is similar to that of ed is lightly or undocumented in spots
+ * (e.g. the regular expression code).  Newer code (e.g. open and visual)
+ * is much more carefully documented, and still rough in spots.
+ *
+ * Please forward bug reports to
+ *
+ *     Bill Joy
+ *     Computer Science Division, EECS
+ *     EVANS HALL
+ *     U.C. Berkeley 94704
+ *     (415) 642-4948
+ *     (415) 642-1024 (dept. office)
+ *
+ * or to wnj@mit-mc on the ARPA-net.  I would particularly like to hear
+ * of additional terminal descriptions you add to the termcap data base.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sgtty.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <sys/stat.h>
+
+extern int errno;
+
+#ifndef VMUNIX
+typedef        short   line;
+#else
+typedef        int     line;
+#endif
+typedef        short   bool;
+
+#include "ex_tune.h"
+#include "ex_vars.h"
+/*
+ * Options in the editor are referred to usually by "value(name)" where
+ * name is all uppercase, i.e. "value(PROMPT)".  This is actually a macro
+ * which expands to a fixed field in a static structure and so generates
+ * very little code.  The offsets for the option names in the structure
+ * are generated automagically from the structure initializing them in
+ * ex_data.c... see the shell script "makeoptions".
+ */
+struct option {
+       char    *oname;
+       char    *oabbrev;
+       short   otype;          /* Types -- see below */
+       short   odefault;       /* Default value */
+       short   ovalue;         /* Current value */
+       char    *osvalue;
+};
+
+#define        ONOFF   0
+#define        NUMERIC 1
+#define        STRING  2               /* SHELL or DIRECTORY */
+#define        OTERM   3
+
+#define        value(a)        options[a].ovalue
+#define        svalue(a)       options[a].osvalue
+
+struct option options[NOPTS + 1];
+
+
+/*
+ * The editor does not normally use the standard i/o library.  Because
+ * we expect the editor to be a heavily used program and because it
+ * does a substantial amount of input/output processing it is appropriate
+ * for it to call low level read/write primitives directly.  In fact,
+ * when debugging the editor we use the standard i/o library.  In any
+ * case the editor needs a printf which prints through "putchar" ala the
+ * old version 6 printf.  Thus we normally steal a copy of the "printf.c"
+ * and "strout" code from the standard i/o library and mung it for our
+ * purposes to avoid dragging in the stdio library headers, etc if we
+ * are not debugging.  Such a modified printf exists in "printf.c" here.
+ */
+#ifdef TRACE
+#      include <stdio.h>
+       FILE    *trace;
+       bool    trubble;
+       bool    techoin;
+       char    tracbuf[BUFSIZ];
+#      undef   putchar
+#      undef   getchar
+#else
+#ifdef VMUNIX
+#      define  BUFSIZ  1024
+#else
+#      define  BUFSIZ  512
+#endif
+#      define  NULL    0
+#      define  EOF     -1
+#endif
+
+/*
+ * Character constants and bits
+ *
+ * The editor uses the QUOTE bit as a flag to pass on with characters
+ * e.g. to the putchar routine.  The editor never uses a simple char variable.
+ * Only arrays of and pointers to characters are used and parameters and
+ * registers are never declared character.
+ */
+#define        QUOTE   0200
+#define        TRIM    0177
+#define        CTRL(c) ('c' & 037)
+#define        NL      CTRL(j)
+#define        CR      CTRL(m)
+#define        DELETE  0177            /* See also ATTN, QUIT in ex_tune.h */
+#define        ESCAPE  033
+
+/*
+ * Miscellaneous random variables used in more than one place
+ */
+bool   aiflag;                 /* Append/change/insert with autoindent */
+bool   anymarks;               /* We have used '[a-z] */
+int    chng;                   /* Warn "No write" */
+char   *Command;
+short  defwind;                /* -w# change default window size */
+int    dirtcnt;                /* When >= MAXDIRT, should sync temporary */
+bool   edited;                 /* Current file is [Edited] */
+line   *endcore;               /* Last available core location */
+bool   endline;                /* Last cmd mode command ended with \n */
+#ifndef VMUNIX
+short  erfile;                 /* Error message file unit */
+#endif
+line   *fendcore;              /* First address in line pointer space */
+char   file[FNSIZE];           /* Working file name */
+char   genbuf[LBSIZE];         /* Working buffer when manipulating linebuf */
+bool   hush;                   /* Command line option - was given, hush up! */
+char   *globp;                 /* (Untyped) input string to command mode */
+bool   holdcm;                 /* Don't cursor address */
+bool   inglobal;               /* Inside g//... or v//... */
+char   *initev;                /* Initial : escape for visual */
+bool   inopen;                 /* Inside open or visual */
+char   *input;                 /* Current position in cmd line input buffer */
+bool   intty;                  /* Input is a tty */
+short  io;                     /* General i/o unit (auto-closed on error!) */
+short  lastc;                  /* Last character ret'd from cmd input */
+bool   laste;                  /* Last command was an "e" (or "rec") */
+char   lastmac;                /* Last macro called for ** */
+char   lasttag[TAGSIZE];       /* Last argument to a tag command */
+char   *linebp;                /* Used in substituting in \n */
+char   linebuf[LBSIZE];        /* The primary line buffer */
+bool   listf;                  /* Command should run in list mode */
+char   *loc1;                  /* Where re began to match (in linebuf) */
+char   *loc2;                  /* First char after re match (") */
+line   names['z'-'a'+2];       /* Mark registers a-z,' */
+int    notecnt;                /* Count for notify (to visual from cmd) */
+bool   numberf;                /* Command should run in number mode */
+char   obuf[BUFSIZ];           /* Buffer for tty output */
+short  ospeed;                 /* Output speed (from gtty) */
+int    otchng;                 /* Backup tchng to find changes in macros */
+short  peekc;                  /* Peek ahead character (cmd mode input) */
+char   *pkill[2];              /* Trim for put with ragged (LISP) delete */
+bool   pfast;                  /* Have stty -nl'ed to go faster */
+int    pid;                    /* Process id of child */
+int    ppid;                   /* Process id of parent (e.g. main ex proc) */
+jmp_buf        resetlab;               /* For error throws to top level (cmd mode) */
+int    rpid;                   /* Pid returned from wait() */
+bool   ruptible;               /* Interruptible is normal state */
+bool   shudclob;               /* Have a prompt to clobber (e.g. on ^D) */
+int    status;                 /* Status returned from wait() */
+int    tchng;                  /* If nonzero, then [Modified] */
+short  tfile;                  /* Temporary file unit */
+bool   vcatch;                 /* Want to catch an error (open/visual) */
+jmp_buf        vreslab;                /* For error throws to a visual catch */
+int    xchng;                  /* Suppresses multiple "No writes" in !cmd */
+
+/*
+ * Macros
+ */
+#define        CP(a, b)        (ignore(strcpy(a, b)))
+#define ckaw()         {if (chng && value(AUTOWRITE)) wop(0);}
+#define        copy(a,b,c)     Copy((char *) a, (char *) b, c)
+#define        eq(a, b)        ((a) && (b) && strcmp(a, b) == 0)
+#define        getexit(a)      copy(a, resetlab, sizeof (jmp_buf))
+#define        lastchar()      lastc
+#define        outchar(c)      (*Outchar)(c)
+#define        pastwh()        (ignore(skipwh()))
+#define        pline(no)       (*Pline)(no)
+#define        reset()         longjmp(resetlab,1)
+#define        resexit(a)      copy(resetlab, a, sizeof (jmp_buf))
+#define        setexit()       setjmp(resetlab)
+#define        setlastchar(c)  lastc = c
+#define        ungetchar(c)    peekc = c
+
+#define        CATCH           vcatch = 1; if (setjmp(vreslab) == 0) {
+#define        ONERR           } else { vcatch = 0;
+#define        ENDCATCH        } vcatch = 0;
+
+/*
+ * Environment like memory
+ */
+char   altfile[FNSIZE];        /* Alternate file name */
+char   direct[32];             /* Temp file goes here */
+char   shell[32];              /* Copied to be settable */
+char   ttytype[16];            /* A long and pretty name */
+char   uxb[UXBSIZE + 2];       /* Last !command for !! */
+
+/*
+ * The editor data structure for accessing the current file consists
+ * of an incore array of pointers into the temporary file tfile.
+ * Each pointer is 15 bits (the low bit is used by global) and is
+ * padded with zeroes to make an index into the temp file where the
+ * actual text of the line is stored.
+ *
+ * To effect undo, copies of affected lines are saved after the last
+ * line considered to be in the buffer, between dol and unddol.
+ * During an open or visual, which uses the command mode undo between
+ * dol and unddol, a copy of the entire, pre-command buffer state
+ * is saved between unddol and truedol.
+ */
+line   *addr1;                 /* First addressed line in a command */
+line   *addr2;                 /* Second addressed line */
+line   *dol;                   /* Last line in buffer */
+line   *dot;                   /* Current line */
+line   *one;                   /* First line */
+line   *truedol;               /* End of all lines, including saves */
+line   *unddol;                /* End of undo saved lines */
+line   *zero;                  /* Points to empty slot before one */
+
+/*
+ * Undo information
+ *
+ * For most commands we save lines changed by salting them away between
+ * dol and unddol before they are changed (i.e. we save the descriptors
+ * into the temp file tfile which is never garbage collected).  The
+ * lines put here go back after unddel, and to complete the undo
+ * we delete the lines [undap1,undap2).
+ *
+ * Undoing a move is much easier and we treat this as a special case.
+ * Similarly undoing a "put" is a special case for although there
+ * are lines saved between dol and unddol we don't stick these back
+ * into the buffer.
+ */
+short  undkind;
+
+line   *unddel;                /* Saved deleted lines go after here */
+line   *undap1;                /* Beginning of new lines */
+line   *undap2;                /* New lines end before undap2 */
+line   *undadot;               /* If we saved all lines, dot reverts here */
+
+#define        UNDCHANGE       0
+#define        UNDMOVE         1
+#define        UNDALL          2
+#define        UNDNONE         3
+#define        UNDPUT          4
+
+/*
+ * Function type definitions
+ */
+#define        NOSTR   (char *) 0
+#define        NOLINE  (line *) 0
+
+int    (*Outchar)();
+int    (*Pline)();
+int    (*Putchar)();
+int    (*oldhup)();
+int    (*setlist())();
+int    (*setnorm())();
+int    (*setnorm())();
+int    (*setnumb())();
+line   *address();
+char   *cgoto();
+char   *genindent();
+char   *getblock();
+char   *getenv();
+line   *getmark();
+char   *longname();
+char   *mesg();
+char   *place();
+char   *plural();
+line   *scanfor();
+line   *setin();
+char   *strcat();
+char   *strcpy();
+char   *strend();
+char   *tailpath();
+char   *tgetstr();
+char   *tgoto();
+char   *ttyname();
+line   *vback();
+char   *vfindcol();
+char   *vgetline();
+char   *vinit();
+char   *vpastwh();
+char   *vskipwh();
+int    put();
+int    putreg();
+int    YANKreg();
+int    delete();
+int    execl();
+int    filter();
+int    getfile();
+int    getsub();
+int    gettty();
+int    join();
+int    listchar();
+off_t  lseek();
+int    normchar();
+int    normline();
+int    numbline();
+int    (*oldquit)();
+int    onhup();
+int    onintr();
+int    putch();
+int    shift();
+int    termchar();
+int    vfilter();
+#ifdef CBREAK
+int    vintr();
+#endif
+int    vputch();
+int    vshftop();
+int    yank();
+
+/*
+ * C doesn't have a (void) cast, so we have to fake it for lint's sake.
+ */
+#ifdef lint
+#      define  ignore(a)       Ignore((char *) (a))
+#      define  ignorf(a)       Ignorf((int (*) ()) (a))
+#else
+#      define  ignore(a)       a
+#      define  ignorf(a)       a
+#endif
diff --git a/usr/src/cmd/ex/ex_addr.c b/usr/src/cmd/ex/ex_addr.c
new file mode 100644 (file)
index 0000000..5621e5e
--- /dev/null
@@ -0,0 +1,299 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_re.h"
+
+/*
+ * Routines for address parsing and assignment and checking of address bounds
+ * in command mode.  The routine address is called from ex_cmds.c
+ * to parse each component of a command (terminated by , ; or the beginning
+ * of the command itself.  It is also called by the scanning routine
+ * in ex_voperate.c from within open/visual.
+ *
+ * Other routines here manipulate the externals addr1 and addr2.
+ * These are the first and last lines for the current command.
+ *
+ * The variable bigmove remembers whether a non-local glitch of . was
+ * involved in an address expression, so we can set the previous context
+ * mark '' when such a motion occurs.
+ */
+
+static bool bigmove;
+
+/*
+ * Set up addr1 and addr2 for commands whose default address is dot.
+ */
+setdot()
+{
+
+       setdot1();
+       if (bigmove)
+               markDOT();
+}
+
+/*
+ * Call setdot1 to set up default addresses without ever
+ * setting the previous context mark.
+ */
+setdot1()
+{
+
+       if (addr2 == 0)
+               addr1 = addr2 = dot;
+       if (addr1 > addr2) {
+               notempty();
+               error("Addr1 > addr2|First address exceeds second");
+       }
+}
+
+/*
+ * Ex allows you to say
+ *     delete 5
+ * to delete 5 lines, etc.
+ * Such nonsense is implemented by setcount.
+ */
+setcount()
+{
+       register int cnt;
+
+       pastwh();
+       if (!isdigit(peekchar())) {
+               setdot();
+               return;
+       }
+       addr1 = addr2;
+       setdot();
+       cnt = getnum();
+       if (cnt <= 0)
+               error("Bad count|Nonzero count required");
+       addr2 += cnt - 1;
+       if (addr2 > dol)
+               addr2 = dol;
+       nonzero();
+}
+
+/*
+ * Parse a number out of the command input stream.
+ */
+getnum()
+{
+       register int cnt;
+
+       for (cnt = 0; isdigit(peekcd());)
+               cnt = cnt * 10 + getchar() - '0';
+       return (cnt);
+}
+
+/*
+ * Set the default addresses for commands which use the whole
+ * buffer as default, notably write.
+ */
+setall()
+{
+
+       if (addr2 == 0) {
+               addr1 = one;
+               addr2 = dol;
+               if (dol == zero) {
+                       dot = zero;
+                       return;
+               }
+       }
+       /*
+        * Don't want to set previous context mark so use setdot1().
+        */
+       setdot1();
+}
+
+/*
+ * No address allowed on, e.g. the file command.
+ */
+setnoaddr()
+{
+
+       if (addr2 != 0)
+               error("No address allowed@on this command");
+}
+
+/*
+ * Parse an address.
+ * Just about any sequence of address characters is legal.
+ *
+ * If you are tricky you can use this routine and the = command
+ * to do simple addition and subtraction of cardinals less
+ * than the number of lines in the file.
+ */
+line *
+address(inline)
+       char *inline;
+{
+       register line *addr;
+       register int offset, c;
+       short lastsign;
+
+       bigmove = 0;
+       lastsign = 0;
+       offset = 0;
+       addr = 0;
+       for (;;) {
+               if (isdigit(peekcd())) {
+                       if (addr == 0) {
+                               addr = zero;
+                               bigmove = 1;
+                       }
+                       loc1 = 0;
+                       addr += offset;
+                       offset = getnum();
+                       if (lastsign >= 0)
+                               addr += offset;
+                       else
+                               addr -= offset;
+                       lastsign = 0;
+                       offset = 0;
+               }
+               switch (c = getcd()) {
+
+               case '?':
+               case '/':
+               case '$':
+               case '\'':
+               case '\\':
+                       bigmove++;
+               case '.':
+                       if (addr || offset)
+                               error("Badly formed address");
+               }
+               offset += lastsign;
+               lastsign = 0;
+               switch (c) {
+
+               case ' ':
+               case '\t':
+                       continue;
+
+               case '+':
+                       lastsign = 1;
+                       if (addr == 0)
+                               addr = dot;
+                       continue;
+
+               case '^':
+               case '-':
+                       lastsign = -1;
+                       if (addr == 0)
+                               addr = dot;
+                       continue;
+
+               case '\\':
+               case '?':
+               case '/':
+                       c = compile(c, 1);
+                       notempty();
+                       savere(scanre);
+                       addr = dot;
+                       if (inline && execute(0, dot)) {
+                               if (c == '/') {
+                                       while (loc1 <= inline)
+                                               if (!execute(1))
+                                                       goto nope;
+                                       break;
+                               } else if (loc1 < inline) {
+                                       char *last;
+doques:
+
+                                       do {
+                                               last = loc1;
+                                               if (!execute(1))
+                                                       break;
+                                       } while (loc1 < inline);
+                                       loc1 = last;
+                                       break;
+                               }
+                       }
+nope:
+                       for (;;) {
+                               if (c == '/') {
+                                       addr++;
+                                       if (addr > dol) {
+                                               if (value(WRAPSCAN) == 0)
+error("No match to BOTTOM|Address search hit BOTTOM without matching pattern");
+                                               addr = zero;
+                                       }
+                               } else {
+                                       addr--;
+                                       if (addr < zero) {
+                                               if (value(WRAPSCAN) == 0)
+error("No match to TOP|Address search hit TOP without matching pattern");
+                                               addr = dol;
+                                       }
+                               }
+                               if (execute(0, addr)) {
+                                       if (inline && c == '?') {
+                                               inline = &linebuf[LBSIZE];
+                                               goto doques;
+                                       }
+                                       break;
+                               }
+                               if (addr == dot)
+                                       error("Fail|Pattern not found");
+                       }
+                       continue;
+
+               case '$':
+                       addr = dol;
+                       continue;
+
+               case '.':
+                       addr = dot;
+                       continue;
+
+               case '\'':
+                       c = markreg(getchar());
+                       if (c == 0)
+                               error("Marks are ' and a-z");
+                       addr = getmark(c);
+                       if (addr == 0)
+                               error("Undefined mark@referenced");
+                       break;
+
+               default:
+                       ungetchar(c);
+                       if (offset) {
+                               if (addr == 0)
+                                       addr = dot;
+                               addr += offset;
+                               loc1 = 0;
+                       }
+                       if (addr == 0) {
+                               bigmove = 0;
+                               return (0);
+                       }
+                       if (addr != zero)
+                               notempty();
+                       addr += lastsign;
+                       if (addr < zero)
+                               error("Negative address@- first buffer line is 1");
+                       if (addr > dol)
+                               error("Not that many lines@in buffer");
+                       return (addr);
+               }
+       }
+}
+
+/*
+ * Abbreviations to make code smaller
+ * Left over from squashing ex version 1.1 into
+ * 11/34's and 11/40's.
+ */
+setCNL()
+{
+
+       setcount();
+       newline();
+}
+
+setNAEOL()
+{
+
+       setnoaddr();
+       eol();
+}
diff --git a/usr/src/cmd/ex/ex_argv.h b/usr/src/cmd/ex/ex_argv.h
new file mode 100644 (file)
index 0000000..666045b
--- /dev/null
@@ -0,0 +1,26 @@
+/* Copyright (c) 1979 Regents of the University of California */
+/*
+ * The current implementation of the argument list is poor,
+ * using an argv even for internally done "next" commands.
+ * It is not hard to see that this is restrictive and a waste of
+ * space.  The statically allocated glob structure could be replaced
+ * by a dynamically allocated argument area space.
+ */
+char   **argv;
+char   **argv0;
+char   *args;
+char   *args0;
+short  argc;
+short  argc0;
+short  morargc;                /* Used with "More files to edit..." */
+
+int    firstln;                /* From +lineno */
+char   *firstpat;              /* From +/pat   */
+
+/* Yech... */
+struct glob {
+       short   argc;                   /* Index of current file in argv */
+       short   argc0;                  /* Number of arguments in argv */
+       char    *argv[NARGS + 1];       /* WHAT A WASTE! */
+       char    argspac[NCARGS + sizeof (int)];
+} frob;
diff --git a/usr/src/cmd/ex/ex_cmds.c b/usr/src/cmd/ex/ex_cmds.c
new file mode 100644 (file)
index 0000000..d577f89
--- /dev/null
@@ -0,0 +1,791 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_argv.h"
+#include "ex_temp.h"
+#include "ex_tty.h"
+
+bool   pflag, nflag;
+int    poffset;
+
+#define        nochng()        lchng = chng
+
+/*
+ * Main loop for command mode command decoding.
+ * A few commands are executed here, but main function
+ * is to strip command addresses, do a little address oriented
+ * processing and call command routines to do the real work.
+ */
+commands(noprompt, exitoneof)
+       bool noprompt, exitoneof;
+{
+       register line *addr;
+       register int c;
+       register int lchng;
+       int given;
+       int seensemi;
+       int cnt;
+       bool hadpr;
+
+       resetflav();
+       nochng();
+       for (;;) {
+               /*
+                * If dot at last command
+                * ended up at zero, advance to one if there is a such.
+                */
+               if (dot <= zero) {
+                       dot = zero;
+                       if (dol > zero)
+                               dot = one;
+               }
+               shudclob = 0;
+
+               /*
+                * If autoprint or trailing print flags,
+                * print the line at the specified offset
+                * before the next command.
+                */
+               if (pflag ||
+                   lchng != chng && value(AUTOPRINT) && !inglobal && !inopen && endline) {
+                       pflag = 0;
+                       nochng();
+                       if (dol != zero) {
+                               addr1 = addr2 = dot + poffset;
+                               if (addr1 < one || addr1 > dol)
+error("Offset out-of-bounds|Offset after command too large");
+                               setdot1();
+                               goto print;
+                       }
+               }
+               nochng();
+
+               /*
+                * Print prompt if appropriate.
+                * If not in global flush output first to prevent
+                * going into pfast mode unreasonably.
+                */
+               if (inglobal == 0) {
+                       flush();
+                       if (!hush && value(PROMPT) && !globp && !noprompt && endline) {
+                               putchar(':');
+                               hadpr = 1;
+                       }
+                       TSYNC();
+               }
+
+               /*
+                * Gobble up the address.
+                * Degenerate addresses yield ".".
+                */
+               addr2 = 0;
+               given = seensemi = 0;
+               do {
+                       addr1 = addr2;
+                       addr = address(0);
+                       c = getcd();
+                       if (addr == 0)
+                               if (c == ',')
+                                       addr = dot;
+                               else if (addr1 != 0) {
+                                       addr2 = dot;
+                                       break;
+                               } else
+                                       break;
+                       addr2 = addr;
+                       given++;
+                       if (c == ';') {
+                               c = ',';
+                               dot = addr;
+                               seensemi = 1;
+                       }
+               } while (c == ',');
+               if (c == '%') {
+                       /* %: same as 1,$ */
+                       addr1 = one;
+                       addr2 = dol;
+                       given = 2;
+                       c = getchar();
+               }
+               if (addr1 == 0)
+                       addr1 = addr2;
+               if (c == ':')
+                       c = getchar();
+
+               /*
+                * Set command name for special character commands.
+                */
+               tailspec(c);
+
+               /*
+                * If called via : escape from open or visual, limit
+                * the set of available commands here to save work below.
+                */
+               if (inopen) {
+                       if (c=='\n' || c=='\r' || c==CTRL(d) || c==EOF) {
+                               if (addr2)
+                                       dot = addr2;
+                               if (c == EOF)
+                                       return;
+                               continue;
+                       }
+                       if (any(c, "o"))
+notinvis:
+                               tailprim(Command, 1, 1);
+               }
+choice:
+               switch (c) {
+
+               case 'a':
+
+                       if (peekchar() == 'r') {
+/* args */
+                               tail("args");
+                               setnoaddr();
+                               eol();
+                               pargs();
+                               continue;
+                       }
+
+/* append */
+                       if (inopen)
+                               goto notinvis;
+                       tail("append");
+                       setdot();
+                       aiflag = exclam();
+                       newline();
+                       deletenone();
+                       setin(addr2);
+                       ignore(append(gettty, addr2));
+                       nochng();
+                       continue;
+
+               case 'c':
+                       switch (peekchar()) {
+
+/* copy */
+                       case 'o':
+                               tail("copy");
+                               move();
+                               continue;
+
+#ifdef CHDIR
+/* cd */
+                       case 'd':
+                               tail("cd");
+                               goto changdir;
+
+/* chdir */
+                       case 'h':
+                               ignchar();
+                               if (peekchar() == 'd') {
+                                       register char *p;
+                                       tail2of("chdir");
+changdir:
+                                       if (savedfile[0] == '/' || !value(WARN))
+                                               ignore(exclam());
+                                       else
+                                               ignore(quickly());
+                                       if (skipend()) {
+                                               p = getenv("HOME");
+                                               if (p == NULL)
+                                                       error("Home directory unknown");
+                                       } else
+                                               getone(), p = file;
+                                       eol();
+                                       if (chdir(p) < 0)
+                                               filioerr(p);
+                                       if (savedfile[0] != '/')
+                                               edited = 0;
+                                       continue;
+                               }
+                               if (inopen)
+                                       tailprim("change", 2, 1);
+                               tail2of("change");
+                               break;
+
+#endif
+                       default:
+                               if (inopen)
+                                       goto notinvis;
+                               tail("change");
+                               break;
+                       }
+/* change */
+                       aiflag = exclam();
+                       setCNL();
+                       setin(addr1);
+                       delete(0);
+                       ignore(append(gettty, addr1 - 1));
+                       nochng();
+                       continue;
+
+/* delete */
+               case 'd':
+                       /*
+                        * Caution: dp and dl have special meaning already.
+                        */
+                       tail("delete");
+                       c = cmdreg();
+                       setCNL();
+                       if (c)
+                               YANKreg(c);
+                       delete(0);
+                       appendnone();
+                       continue;
+
+/* edit */
+/* ex */
+               case 'e':
+                       tail(peekchar() == 'x' ? "ex" : "edit");
+                       if (!exclam() && chng)
+                               c = 'E';
+                       filename(c);
+                       if (c == 'E') {
+                               ungetchar(lastchar());
+                               ignore(quickly());
+                       }
+                       setnoaddr();
+doecmd:
+                       init();
+                       addr2 = zero;
+                       laste++;
+                       sync();
+                       rop(c);
+                       nochng();
+                       continue;
+
+/* file */
+               case 'f':
+                       tail("file");
+                       setnoaddr();
+                       filename(c);
+                       noonl();
+/*
+                       synctmp();
+*/
+                       continue;
+
+/* global */
+               case 'g':
+                       tail("global");
+                       global(!exclam());
+                       nochng();
+                       continue;
+
+/* insert */
+               case 'i':
+                       if (inopen)
+                               goto notinvis;
+                       tail("insert");
+                       setdot();
+                       nonzero();
+                       aiflag = exclam();
+                       newline();
+                       deletenone();
+                       setin(addr2);
+                       ignore(append(gettty, addr2 - 1));
+                       if (dot == zero && dol > zero)
+                               dot = one;
+                       nochng();
+                       continue;
+
+/* join */
+               case 'j':
+                       tail("join");
+                       c = exclam();
+                       setcount();
+                       nonzero();
+                       newline();
+                       if (given < 2 && addr2 != dol)
+                               addr2++;
+                       join(c);
+                       continue;
+
+/* k */
+               case 'k':
+casek:
+                       pastwh();
+                       c = getchar();
+                       if (endcmd(c))
+                               serror("Mark what?|%s requires following letter", Command);
+                       newline();
+                       if (!islower(c))
+                               error("Bad mark|Mark must specify a letter");
+                       setdot();
+                       nonzero();
+                       names[c - 'a'] = *addr2 &~ 01;
+                       anymarks = 1;
+                       continue;
+
+/* list */
+               case 'l':
+                       tail("list");
+                       setCNL();
+                       ignorf(setlist(1));
+                       pflag = 0;
+                       goto print;
+
+               case 'm':
+                       if (peekchar() == 'a') {
+                               ignchar();
+                               if (peekchar() == 'p') {
+/* map */
+                                       tail2of("map");
+                                       setnoaddr();
+                                       mapcmd(0);
+                                       continue;
+                               }
+/* mark */
+                               tail2of("mark");
+                               goto casek;
+                       }
+/* move */
+                       tail("move");
+                       move();
+                       continue;
+
+               case 'n':
+                       if (peekchar() == 'u') {
+                               tail("number");
+                               goto numberit;
+                       }
+/* next */
+                       tail("next");
+                       setnoaddr();
+                       ckaw();
+                       ignore(quickly());
+                       if (getargs())
+                               makargs();
+                       next();
+                       c = 'e';
+                       filename(c);
+                       goto doecmd;
+
+/* open */
+               case 'o':
+                       tail("open");
+                       oop();
+                       pflag = 0;
+                       nochng();
+                       continue;
+
+               case 'p':
+               case 'P':
+                       switch (peekchar()) {
+
+/* put */
+                       case 'u':
+                               tail("put");
+                               setdot();
+                               c = cmdreg();
+                               eol();
+                               if (c)
+                                       putreg(c);
+                               else
+                                       put();
+                               continue;
+
+                       case 'r':
+                               ignchar();
+                               if (peekchar() == 'e') {
+/* preserve */
+                                       tail2of("preserve");
+                                       eol();
+                                       if (preserve() == 0)
+                                               error("Preserve failed!");
+                                       else
+                                               error("File preserved.");
+                               }
+                               tail2of("print");
+                               break;
+
+                       default:
+                               tail("print");
+                               break;
+                       }
+/* print */
+                       setCNL();
+                       pflag = 0;
+print:
+                       nonzero();
+                       if (CL && span() > LINES) {
+                               flush1();
+                               vclear();
+                       }
+                       plines(addr1, addr2, 1);
+                       continue;
+
+/* quit */
+               case 'q':
+                       tail("quit");
+                       setnoaddr();
+                       c = quickly();
+                       eol();
+                       if (!c)
+quit:
+                               nomore();
+                       if (inopen) {
+                               vgoto(WECHO, 0);
+                               if (!ateopr())
+                                       vnfl();
+                               else {
+                                       putpad(VE);
+                                       putpad(KE);
+                               }
+                               flush();
+                               setty(normf);
+                       }
+                       cleanup(1);
+                       exit(0);
+
+               case 'r':
+                       if (peekchar() == 'e') {
+                               ignchar();
+                               switch (peekchar()) {
+
+/* rewind */
+                               case 'w':
+                                       tail2of("rewind");
+                                       setnoaddr();
+                                       ignore(quickly());
+                                       eol();
+                                       erewind();
+                                       next();
+                                       c = 'e';
+                                       ungetchar(lastchar());
+                                       filename(c);
+                                       goto doecmd;
+
+/* recover */
+                               case 'c':
+                                       tail2of("recover");
+                                       setnoaddr();
+                                       c = 'e';
+                                       if (!exclam() && chng)
+                                               c = 'E';
+                                       filename(c);
+                                       if (c == 'E') {
+                                               ungetchar(lastchar());
+                                               ignore(quickly());
+                                       }
+                                       init();
+                                       addr2 = zero;
+                                       laste++;
+                                       sync();
+                                       recover();
+                                       rop2();
+                                       revocer();
+                                       if (status == 0)
+                                               rop3(c);
+                                       if (dol != zero)
+                                               change();
+                                       nochng();
+                                       continue;
+                               }
+                               tail2of("read");
+                       } else
+                               tail("read");
+/* read */
+                       if (savedfile[0] == 0 && dol == zero)
+                               c = 'e';
+                       pastwh();
+                       if (peekchar() == '!') {
+                               setdot();
+                               ignchar();
+                               unix0(0);
+                               filter(0);
+                               continue;
+                       }
+                       filename(c);
+                       rop(c);
+                       nochng();
+                       if (inopen && endline && addr1 > zero && addr1 < dol)
+                               dot = addr1 + 1;
+                       continue;
+
+               case 's':
+                       switch (peekchar()) {
+                       /*
+                        * Caution: 2nd char cannot be c, g, or r
+                        * because these have meaning to substitute.
+                        */
+
+/* set */
+                       case 'e':
+                               tail("set");
+                               setnoaddr();
+                               set();
+                               continue;
+
+/* shell */
+                       case 'h':
+                               tail("shell");
+                               setNAEOL();
+                               vnfl();
+                               putpad(TE);
+                               flush();
+                               unixwt(1, unixex("-i", (char *) 0, 0, 0));
+                               vcontin(0);
+                               putpad(TI);
+                               continue;
+
+/* source */
+                       case 'o':
+                               if (inopen)
+                                       goto notinvis;
+                               tail("source");
+                               setnoaddr();
+                               getone();
+                               eol();
+                               source(file, 0);
+                               continue;
+                       }
+                       /* fall into ... */
+
+/* & */
+/* ~ */
+/* substitute */
+               case '&':
+               case '~':
+                       Command = "substitute";
+                       if (c == 's')
+                               tail(Command);
+                       if (!substitute(c))
+                               pflag = 0;
+                       continue;
+
+/* t */
+               case 't':
+                       if (peekchar() == 'a') {
+                               tail("tag");
+                               tagfind(exclam());
+                               if (!inopen)
+                                       lchng = chng - 1;
+                               else
+                                       nochng();
+                               continue;
+                       }
+                       tail("t");
+                       move();
+                       continue;
+
+               case 'u':
+                       if (peekchar() == 'n') {
+/* unmap */
+                               ignchar();
+                               if (peekchar() == 'm') {
+                                       tail2of("unmap");
+                                       setnoaddr();
+                                       mapcmd(1);
+                                       continue;
+                               }
+/* undo */
+                               tail2of("undo");
+                       } else
+                               tail("undo");
+                       setnoaddr();
+                       markDOT();
+                       c = exclam();
+                       newline();
+                       undo(c);
+                       continue;
+
+               case 'v':
+                       switch (peekchar()) {
+
+                       case 'e':
+/* version */
+                               tail("version");
+                               setNAEOL();
+                               /* should use SCCS subst here */
+                               printf("Version 3.2, January 4, 1980");
+                               noonl();
+                               continue;
+
+/* visual */
+                       case 'i':
+                               tail("visual");
+                               vop();
+                               pflag = 0;
+                               nochng();
+                               continue;
+                       }
+/* v */
+                       tail("v");
+                       global(0);
+                       nochng();
+                       continue;
+
+/* write */
+               case 'w':
+                       c = peekchar();
+                       tail(c == 'q' ? "wq" : "write");
+                       if (skipwh() && peekchar() == '!') {
+                               ignchar();
+                               setall();
+                               unix0(0);
+                               filter(1);
+                       } else {
+                               setall();
+                               wop(1);
+                               nochng();
+                       }
+                       if (c == 'q')
+                               goto quit;
+                       continue;
+
+/* yank */
+               case 'y':
+                       tail("yank");
+                       c = cmdreg();
+                       setcount();
+                       eol();
+                       if (c)
+                               YANKreg(c);
+                       else
+                               yank();
+                       continue;
+
+/* z */
+               case 'z':
+                       zop(0);
+                       pflag = 0;
+                       continue;
+
+/* * */
+/* @ */
+               case '*':
+               case '@':
+                       c = getchar();
+                       if (c=='\n' || c=='\r')
+                               ungetchar(c);
+                       if (any(c, "@*\n\r"))
+                               c = lastmac;
+                       if (isupper(c))
+                               c = tolower(c);
+                       if (!islower(c))
+                               error("Bad register");
+                       newline();
+                       setdot();
+                       cmdmac(c);
+                       continue;
+
+/* | */
+               case '|':
+                       endline = 0;
+                       goto caseline;
+
+/* \n */
+               case '\n':
+                       endline = 1;
+caseline:
+                       notempty();
+                       if (addr2 == 0) {
+                               if (dot == dol)
+                                       error("At EOF|At end-of-file");
+                               if (UP != NOSTR && c == '\n' && !inglobal)
+                                       c = CTRL(k);
+                               if (inglobal)
+                                       addr1 = addr2 = dot;
+                               else
+                                       addr1 = addr2 = dot + 1;
+                       }
+                       setdot();
+                       nonzero();
+                       if (seensemi)
+                               addr1 = addr2;
+                       getline(*addr1);
+                       if (c == CTRL(k)) {
+                               flush1();
+                               destline--;
+                               if (hadpr)
+                                       shudclob = 1;
+                       }
+                       plines(addr1, addr2, 1);
+                       continue;
+
+/* # */
+               case '#':
+numberit:
+                       setCNL();
+                       ignorf(setnumb(1));
+                       pflag = 0;
+                       goto print;
+
+/* = */
+               case '=':
+                       newline();
+                       setall();
+                       printf("%d", lineno(addr2));
+                       noonl();
+                       continue;
+
+/* ! */
+               case '!':
+                       if (addr2 != 0) {
+                               unix0(0);
+                               setdot();
+                               filter(2);
+                       } else {
+                               unix0(1);
+                               vnfl();
+                               putpad(TE);
+                               flush();
+                               unixwt(1, unixex("-c", uxb, 0, 0));
+                               vcontin(1);
+                               putpad(TI);
+                               nochng();
+                       }
+                       continue;
+
+/* < */
+/* > */
+               case '<':
+               case '>':
+                       for (cnt = 1; peekchar() == c; cnt++)
+                               ignchar();
+                       setCNL();
+                       shift(c, cnt);
+                       continue;
+
+/* ^D */
+/* EOF */
+               case CTRL(d):
+               case EOF:
+                       if (exitoneof) {
+                               if (addr2 != 0)
+                                       dot = addr2;
+                               return;
+                       }
+                       if (!isatty(0)) {
+                               if (intty)
+                                       /*
+                                        * Chtty sys call at UCB may cause a
+                                        * input which was a tty to suddenly be
+                                        * turned into /dev/null.
+                                        */
+                                       onhup();
+                               return;
+                       }
+                       if (addr2 != 0) {
+                               setlastchar('\n');
+                               putnl();
+                       }
+                       if (dol == zero) {
+                               if (addr2 == 0)
+                                       putnl();
+                               notempty();
+                       }
+                       ungetchar(EOF);
+                       zop(hadpr);
+                       continue;
+
+               default:
+                       if (!isalpha(c))
+                               break;
+                       ungetchar(c);
+                       tailprim("", 0, 0);
+               }
+               error("What?|Unknown command character '%c'", c);
+       }
+}
diff --git a/usr/src/cmd/ex/ex_cmds2.c b/usr/src/cmd/ex/ex_cmds2.c
new file mode 100644 (file)
index 0000000..95deb53
--- /dev/null
@@ -0,0 +1,508 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_argv.h"
+#include "ex_temp.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+bool   pflag, nflag;
+int    poffset;
+
+/*
+ * Subroutines for major command loop.
+ */
+
+/*
+ * Is there a single letter indicating a named buffer next?
+ */
+cmdreg()
+{
+       register int c = 0;
+       register int wh = skipwh();
+
+       if (wh && isalpha(peekchar()))
+               c = getchar();
+       return (c);
+}
+
+/*
+ * Tell whether the character ends a command
+ */
+endcmd(ch)
+       int ch;
+{
+       switch (ch) {
+       
+       case '\n':
+       case EOF:
+               endline = 1;
+               return (1);
+       
+       case '|':
+               endline = 0;
+               return (1);
+       }
+       return (0);
+}
+
+/*
+ * Insist on the end of the command.
+ */
+eol()
+{
+
+       if (!skipend())
+               error("Extra chars|Extra characters at end of command");
+       ignnEOF();
+}
+
+/*
+ * Print out the message in the error message file at str,
+ * with i an integer argument to printf.
+ */
+/*VARARGS2*/
+error(str, i)
+#ifdef lint
+       register char *str;
+#else
+       register int str;
+#endif
+       int i;
+{
+
+       error0();
+       merror(str, i);
+       error1(str);
+}
+
+/*
+ * Rewind the argument list.
+ */
+erewind()
+{
+
+       argc = argc0;
+       argv = argv0;
+       args = args0;
+       if (argc > 1 && !hush) {
+               printf(mesg("%d files@to edit"), argc);
+               if (inopen)
+                       putchar(' ');
+               else
+                       putNFL();
+       }
+}
+
+/*
+ * Guts of the pre-printing error processing.
+ * If in visual and catching errors, then we dont mung up the internals,
+ * just fixing up the echo area for the print.
+ * Otherwise we reset a number of externals, and discard unused input.
+ */
+error0()
+{
+
+       if (vcatch) {
+               if (splitw == 0)
+                       fixech();
+               if (!SO || !SE)
+                       dingdong();
+               return;
+       }
+       if (input) {
+               input = strend(input) - 1;
+               if (*input == '\n')
+                       setlastchar('\n');
+               input = 0;
+       }
+       setoutt();
+       flush();
+       resetflav();
+       if (laste) {
+               laste = 0;
+               sync();
+       }
+       if (!SO || !SE)
+               dingdong();
+       if (inopen) {
+               /*
+                * We are coming out of open/visual ungracefully.
+                * Restore COLUMNS, undo, and fix tty mode.
+                */
+               COLUMNS = OCOLUMNS;
+               undvis();
+               ostop(normf);
+               putpad(VE);
+               putpad(KE);
+               putnl();
+       }
+       inopen = 0;
+       holdcm = 0;
+}
+
+/*
+ * Post error printing processing.
+ * Close the i/o file if left open.
+ * If catching in visual then throw to the visual catch,
+ * else if a child after a fork, then exit.
+ * Otherwise, in the normal command mode error case,
+ * finish state reset, and throw to top.
+ */
+error1(str)
+       char *str;
+{
+       bool die;
+
+       if (io > 0) {
+               close(io);
+               io = -1;
+       }
+       die = (getpid() != ppid);       /* Only children die */
+       if (vcatch && !die) {
+               inglobal = 0;
+               vglobp = vmacp = 0;
+               inopen = 1;
+               vcatch = 0;
+               fixol();
+               longjmp(vreslab,1);
+       }
+       if (str && !vcatch)
+               putNFL();
+       if (die)
+               exit(1);
+       lseek(0, 0L, 2);
+       if (inglobal)
+               setlastchar('\n');
+       inglobal = 0;
+       globp = 0;
+       while (lastchar() != '\n' && lastchar() != EOF)
+               ignchar();
+       ungetchar(0);
+       endline = 1;
+       reset();
+}
+
+fixol()
+{
+       if (Outchar != vputchar) {
+               flush();
+               if (state == ONEOPEN || state == HARDOPEN)
+                       outline = destline = 0;
+               Outchar = vputchar;
+               vcontin(1);
+       } else {
+               if (destcol)
+                       vclreol();
+               vclean();
+       }
+}
+
+/*
+ * Does an ! character follow in the command stream?
+ */
+exclam()
+{
+
+       if (peekchar() == '!') {
+               ignchar();
+               return (1);
+       }
+       return (0);
+}
+
+/*
+ * Make an argument list for e.g. next.
+ */
+makargs()
+{
+
+       glob(&frob);
+       argc0 = frob.argc0;
+       argv0 = frob.argv;
+       args0 = argv0[0];
+       erewind();
+}
+
+/*
+ * Advance to next file in argument list.
+ */
+next()
+{
+
+       if (argc == 0)
+               error("No more files@to edit");
+       morargc = argc;
+       if (savedfile[0])
+               CP(altfile, savedfile);
+       CP(savedfile, args);
+       argc--;
+       args = argv ? *++argv : strend(args) + 1;
+}
+
+/*
+ * Eat trailing flags and offsets after a command,
+ * saving for possible later post-command prints.
+ */
+newline()
+{
+       register int c;
+
+       resetflav();
+       for (;;) {
+               c = getchar();
+               switch (c) {
+
+               case '^':
+               case '-':
+                       poffset--;
+                       break;
+
+               case '+':
+                       poffset++;
+                       break;
+
+               case 'l':
+                       listf++;
+                       break;
+
+               case '#':
+                       nflag++;
+                       break;
+
+               case 'p':
+                       listf = 0;
+                       break;
+
+               case ' ':
+               case '\t':
+                       continue;
+
+               default:
+                       if (!endcmd(c))
+serror("Extra chars|Extra characters at end of \"%s\" command", Command);
+                       if (c == EOF)
+                               ungetchar(c);
+                       setflav();
+                       return;
+               }
+               pflag++;
+       }
+}
+
+/*
+ * Before quit or respec of arg list, check that there are
+ * no more files in the arg list.
+ */
+nomore()
+{
+
+       if (argc == 0 || morargc == argc)
+               return;
+       morargc = argc;
+       merror("%d more file", argc);
+       serror("%s@to edit", plural((long) argc));
+}
+
+/*
+ * Before edit of new file check that either an ! follows
+ * or the file has not been changed.
+ */
+quickly()
+{
+
+       if (exclam())
+               return (1);
+       if (chng && dol > zero) {
+/*
+               chng = 0;
+*/
+               xchng = 0;
+               error("No write@since last change (:%s! overrides)", Command);
+       }
+       return (0);
+}
+
+/*
+ * Reset the flavor of the output to print mode with no numbering.
+ */
+resetflav()
+{
+
+       if (inopen)
+               return;
+       listf = 0;
+       nflag = 0;
+       pflag = 0;
+       poffset = 0;
+       setflav();
+}
+
+/*
+ * Print an error message with a %s type argument to printf.
+ * Message text comes from error message file.
+ */
+serror(str, cp)
+#ifdef lint
+       register char *str;
+#else
+       register int str;
+#endif
+       char *cp;
+{
+
+       error0();
+       smerror(str, cp);
+       error1(str);
+}
+
+/*
+ * Set the flavor of the output based on the flags given
+ * and the number and list options to either number or not number lines
+ * and either use normally decoded (ARPAnet standard) characters or list mode,
+ * where end of lines are marked and tabs print as ^I.
+ */
+setflav()
+{
+
+       if (inopen)
+               return;
+       setnumb(nflag || value(NUMBER));
+       setlist(listf || value(LIST));
+       setoutt();
+}
+
+/*
+ * Skip white space and tell whether command ends then.
+ */
+skipend()
+{
+
+       pastwh();
+       return (endcmd(peekchar()));
+}
+
+/*
+ * Set the command name for non-word commands.
+ */
+tailspec(c)
+       int c;
+{
+       static char foocmd[2];
+
+       foocmd[0] = c;
+       Command = foocmd;
+}
+
+/*
+ * Try to read off the rest of the command word.
+ * If alphabetics follow, then this is not the command we seek.
+ */
+tail(comm)
+       char *comm;
+{
+
+       tailprim(comm, 1, 0);
+}
+
+tail2of(comm)
+       char *comm;
+{
+
+       tailprim(comm, 2, 0);
+}
+
+char   tcommand[20];
+
+tailprim(comm, i, notinvis)
+       register char *comm;
+       int i;
+       bool notinvis;
+{
+       register char *cp;
+       register int c;
+
+       Command = comm;
+       for (cp = tcommand; i > 0; i--)
+               *cp++ = *comm++;
+       while (*comm && peekchar() == *comm)
+               *cp++ = getchar(), comm++;
+       c = peekchar();
+       if (notinvis || isalpha(c)) {
+               /*
+                * Of the trailing lp funny business, only dl and dp
+                * survive the move from ed to ex.
+                */
+               if (tcommand[0] == 'd' && any(c, "lp"))
+                       goto ret;
+               if (tcommand[0] == 's' && any(c, "gcr"))
+                       goto ret;
+               while (cp < &tcommand[19] && isalpha(peekchar()))
+                       *cp++ = getchar();
+               *cp = 0;
+               if (notinvis)
+                       serror("What?|%s: No such command from open/visual", tcommand);
+               else
+                       serror("What?|%s: Not an editor command", tcommand);
+       }
+ret:
+       *cp = 0;
+}
+
+/*
+ * Continue after a shell escape from open/visual.
+ */
+vcontin(ask)
+       bool ask;
+{
+
+       if (vcnt > 0)
+               vcnt = -vcnt;
+       if (inopen) {
+               if (state != VISUAL) {
+/*
+                       vtube[WECHO][0] = '*';
+                       vnfl();
+*/
+                       return;
+               }
+               if (ask) {
+                       merror("[Hit return to continue] ");
+                       flush();
+               }
+#ifndef CBREAK
+               vraw();
+#endif
+               if (ask) {
+                       /*
+                        * Gobble ^Q/^S since the tty driver should be eating
+                        * them (as far as the user can see)
+                        */
+                       while (peekkey() == CTRL(Q) || peekkey() == CTRL(S))
+                               ignore(getkey());
+                       if(getkey() == ':')
+                               ungetkey(':');
+               }
+               putpad(VS);
+               putpad(KS);
+       }
+}
+
+/*
+ * Put out a newline (before a shell escape)
+ * if in open/visual.
+ */
+vnfl()
+{
+
+       if (inopen) {
+               if (state != VISUAL && state != CRTOPEN && destline <= WECHO)
+                       vclean();
+               else
+                       vmoveitup(1, 0);
+               vgoto(WECHO, 0);
+               vclrbyte(vtube[WECHO], WCOLS);
+               putpad(VE);
+               putpad(KE);
+       }
+       flush();
+}
diff --git a/usr/src/cmd/ex/ex_cmdsub.c b/usr/src/cmd/ex/ex_cmdsub.c
new file mode 100644 (file)
index 0000000..09c01d9
--- /dev/null
@@ -0,0 +1,1143 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_argv.h"
+#include "ex_temp.h"
+#include "ex_tty.h"
+
+/*
+ * Command mode subroutines implementing
+ *     append, args, copy, delete, join, move, put,
+ *     shift, tag, yank, z and undo
+ */
+
+bool   endline = 1;
+line   *tad1;
+
+/*
+ * Append after line a lines returned by function f.
+ * Be careful about intermediate states to avoid scramble
+ * if an interrupt comes in.
+ */
+append(f, a)
+       int (*f)();
+       line *a;
+{
+       register line *a1, *a2, *rdot;
+       int nline;
+
+       nline = 0;
+       dot = a;
+       /*
+        * This is probably a bug, since it's different than the other tests
+        * in appendnone, delete, and deletenone. It is known to fail for
+        * the command :g/foo/r xxx (where there is one foo and the file
+        * xxx exists) and you try to undo it. I'm leaving it in for now
+        * because I'm afraid if I change it I'll break something.
+        */
+       if (!inglobal && !inopen && f != getsub) {
+               undap1 = undap2 = dot + 1;
+               undkind = UNDCHANGE;
+       }
+       while ((*f)() == 0) {
+               if (truedol >= endcore) {
+                       if (morelines() < 0) {
+                               if (!inglobal && f == getsub) {
+                                       undap1 = addr1;
+                                       undap2 = addr2 + 1;
+                               }
+                               error("Out of memory@- too many lines in file");
+                       }
+               }
+               nline++;
+               a1 = truedol + 1;
+               a2 = a1 + 1;
+               dot++;
+               undap2++;
+               dol++;
+               unddol++;
+               truedol++;
+               for (rdot = dot; a1 > rdot;)
+                       *--a2 = *--a1;
+               *rdot = 0;
+               putmark(rdot);
+               if (f == gettty) {
+                       dirtcnt++;
+                       TSYNC();
+               }
+       }
+       return (nline);
+}
+
+appendnone()
+{
+
+       if (inopen >= 0 && (inopen || !inglobal)) {
+               undkind = UNDCHANGE;
+               undap1 = undap2 = addr1;
+       }
+}
+
+/*
+ * Print out the argument list, with []'s around the current name.
+ */
+pargs()
+{
+       register char **av = argv0, *as = args0;
+       register int ac;
+
+       for (ac = 0; ac < argc0; ac++) {
+               if (ac != 0)
+                       putchar(' ');
+               if (ac + argc == argc0 - 1)
+                       printf("[");
+               lprintf("%s", as);
+               if (ac + argc == argc0 - 1)
+                       printf("]");
+               as = av ? *++av : strend(as) + 1;
+       }
+       noonl();
+}
+
+/*
+ * Delete lines; two cases are if we are really deleting,
+ * more commonly we are just moving lines to the undo save area.
+ */
+delete(hush)
+       bool hush;
+{
+       register line *a1, *a2;
+
+       nonzero();
+       if (inopen >= 0 && (inopen || !inglobal)) {
+               register int (*dsavint)();
+
+               change();
+               dsavint = signal(SIGINT, SIG_IGN);
+               undkind = UNDCHANGE;
+               a1 = addr1;
+               squish();
+               a2 = addr2;
+               if (a2++ != dol) {
+                       reverse(a1, a2);
+                       reverse(a2, dol + 1);
+                       reverse(a1, dol + 1);
+               }
+               dol -= a2 - a1;
+               unddel = a1 - 1;
+               if (a1 > dol)
+                       a1 = dol;
+               dot = a1;
+               pkill[0] = pkill[1] = 0;
+               signal(SIGINT, dsavint);
+       } else {
+               register line *a3;
+               register int i;
+
+               change();
+               a1 = addr1;
+               a2 = addr2 + 1;
+               a3 = truedol;
+               i = a2 - a1;
+               unddol -= i;
+               undap2 -= i;
+               dol -= i;
+               truedol -= i;
+               do
+                       *a1++ = *a2++;
+               while (a2 <= a3);
+               a1 = addr1;
+               if (a1 > dol)
+                       a1 = dol;
+               dot = a1;
+       }
+       if (!hush)
+               killed();
+}
+
+deletenone()
+{
+
+       if (inopen >= 0 && (inopen || !inglobal)) {
+               undkind = UNDCHANGE;
+               squish();
+               unddel = addr1;
+       }
+}
+
+/*
+ * Crush out the undo save area, moving the open/visual
+ * save area down in its place.
+ */
+squish()
+{
+       register line *a1 = dol + 1, *a2 = unddol + 1, *a3 = truedol + 1;
+
+       if (inopen == -1)
+               return;
+       if (a1 < a2 && a2 < a3)
+               do
+                       *a1++ = *a2++;
+               while (a2 < a3);
+       truedol -= unddol - dol;
+       unddol = dol;
+}
+
+/*
+ * Join lines.  Special hacks put in spaces, two spaces if
+ * preceding line ends with '.', or no spaces if next line starts with ).
+ */
+static int jcount, jnoop();
+
+join(c)
+       int c;
+{
+       register line *a1;
+       register char *cp, *cp1;
+
+       cp = genbuf;
+       *cp = 0;
+       for (a1 = addr1; a1 <= addr2; a1++) {
+               getline(*a1);
+               cp1 = linebuf;
+               if (a1 != addr1 && c == 0) {
+                       while (*cp1 == ' ' || *cp1 == '\t')
+                               cp1++;
+                       if (*cp1 && cp > genbuf && cp[-1] != ' ' && cp[-1] != '\t') {
+                               if (*cp1 != ')') {
+                                       *cp++ = ' ';
+                                       if (cp[-2] == '.')
+                                               *cp++ = ' ';
+                               }
+                       }
+               }
+               while (*cp++ = *cp1++)
+                       if (cp > &genbuf[LBSIZE-2])
+                               error("Line overflow|Result line of join would be too long");
+               cp--;
+       }
+       strcLIN(genbuf);
+       delete(0);
+       jcount = 1;
+       ignore(append(jnoop, --addr1));
+}
+
+static
+jnoop()
+{
+
+       return(--jcount);
+}
+
+/*
+ * Move and copy lines.  Hard work is done by move1 which
+ * is also called by undo.
+ */
+int    getcopy();
+
+move()
+{
+       register line *adt;
+       bool iscopy = 0;
+
+       if (Command[0] == 'm') {
+               setdot1();
+               markpr(addr2 == dot ? addr1 - 1 : addr2 + 1);
+       } else {
+               iscopy++;
+               setdot();
+       }
+       nonzero();
+       adt = address(0);
+       if (adt == 0)
+               serror("%s where?|%s requires a trailing address", Command);
+       newline();
+       move1(iscopy, adt);
+       killed();
+}
+
+move1(cflag, addrt)
+       int cflag;
+       line *addrt;
+{
+       register line *adt, *ad1, *ad2;
+       int lines;
+
+       adt = addrt;
+       lines = (addr2 - addr1) + 1;
+       if (cflag) {
+               tad1 = addr1;
+               ad1 = dol;
+               ignore(append(getcopy, ad1++));
+               ad2 = dol;
+       } else {
+               ad2 = addr2;
+               for (ad1 = addr1; ad1 <= ad2;)
+                       *ad1++ &= ~01;
+               ad1 = addr1;
+       }
+       ad2++;
+       if (adt < ad1) {
+               if (adt + 1 == ad1 && !cflag && !inglobal)
+                       error("That move would do nothing!");
+               dot = adt + (ad2 - ad1);
+               if (++adt != ad1) {
+                       reverse(adt, ad1);
+                       reverse(ad1, ad2);
+                       reverse(adt, ad2);
+               }
+       } else if (adt >= ad2) {
+               dot = adt++;
+               reverse(ad1, ad2);
+               reverse(ad2, adt);
+               reverse(ad1, adt);
+       } else
+               error("Move to a moved line");
+       change();
+       if (!inglobal)
+               if (cflag) {
+                       undap1 = addrt + 1;
+                       undap2 = undap1 + lines;
+                       deletenone();
+               } else {
+                       undkind = UNDMOVE;
+                       undap1 = addr1;
+                       undap2 = addr2;
+                       unddel = addrt;
+                       squish();
+               }
+}
+
+getcopy()
+{
+
+       if (tad1 > addr2)
+               return (EOF);
+       getline(*tad1++);
+       return (0);
+}
+
+/*
+ * Put lines in the buffer from the undo save area.
+ */
+getput()
+{
+
+       if (tad1 > unddol)
+               return (EOF);
+       getline(*tad1++);
+       tad1++;
+       return (0);
+}
+
+put()
+{
+       register int cnt;
+
+       cnt = unddol - dol;
+       if (cnt && inopen && pkill[0] && pkill[1]) {
+               pragged(1);
+               return;
+       }
+       tad1 = dol + 1;
+       ignore(append(getput, addr2));
+       undkind = UNDPUT;
+       notecnt = cnt;
+       netchange(cnt);
+}
+
+/*
+ * A tricky put, of a group of lines in the middle
+ * of an existing line.  Only from open/visual.
+ * Argument says pkills have meaning, e.g. called from
+ * put; it is 0 on calls from putreg.
+ */
+pragged(kill)
+       bool kill;
+{
+       extern char *cursor;
+       register char *gp = &genbuf[cursor - linebuf];
+
+       /*
+        * This kind of stuff is TECO's forte.
+        * We just grunge along, since it cuts
+        * across our line-oriented model of the world
+        * almost scrambling our addled brain.
+        */
+       if (!kill)
+               getDOT();
+       strcpy(genbuf, linebuf);
+       getline(*unddol);
+       if (kill)
+               *pkill[1] = 0;
+       strcat(linebuf, gp);
+       putmark(unddol);
+       getline(dol[1]);
+       if (kill)
+               strcLIN(pkill[0]);
+       strcpy(gp, linebuf);
+       strcLIN(genbuf);
+       putmark(dol+1);
+       undkind = UNDCHANGE;
+       undap1 = dot;
+       undap2 = dot + 1;
+       unddel = dot - 1;
+       undo(1);
+}
+
+/*
+ * Shift lines, based on c.
+ * If c is neither < nor >, then this is a lisp aligning =.
+ */
+shift(c, cnt)
+       int c;
+       int cnt;
+{
+       register line *addr;
+       register char *cp;
+       char *dp;
+       register int i;
+
+       if (!inglobal)
+               save12(), undkind = UNDCHANGE;
+       cnt *= value(SHIFTWIDTH);
+       for (addr = addr1; addr <= addr2; addr++) {
+               dot = addr;
+#ifdef LISPCODE
+               if (c == '=' && addr == addr1 && addr != addr2)
+                       continue;
+#endif
+               getDOT();
+               i = whitecnt(linebuf);
+               switch (c) {
+
+               case '>':
+                       if (linebuf[0] == 0)
+                               continue;
+                       cp = genindent(i + cnt);
+                       break;
+
+               case '<':
+                       if (i == 0)
+                               continue;
+                       i -= cnt;
+                       cp = i > 0 ? genindent(i) : genbuf;
+                       break;
+
+#ifdef LISPCODE
+               default:
+                       i = lindent(addr);
+                       getDOT();
+                       cp = genindent(i);
+                       break;
+#endif
+               }
+               if (cp + strlen(dp = vpastwh(linebuf)) >= &genbuf[LBSIZE - 2])
+                       error("Line too long|Result line after shift would be too long");
+               CP(cp, dp);
+               strcLIN(genbuf);
+               putmark(addr);
+       }
+       killed();
+}
+
+/*
+ * Find a tag in the tags file.
+ * Most work here is in parsing the tags file itself.
+ */
+tagfind(quick)
+       bool quick;
+{
+       char cmdbuf[BUFSIZ];
+       char filebuf[FNSIZE];
+       register int c, d;
+       bool samef = 1;
+       bool notagsfile = 0;
+       short master = -1;
+       short omagic;
+
+       omagic = value(MAGIC);
+       if (!skipend()) {
+               register char *lp = lasttag;
+
+               while (!iswhite(peekchar()) && !endcmd(peekchar()))
+                       if (lp < &lasttag[sizeof lasttag - 2])
+                               *lp++ = getchar();
+                       else
+                               ignchar();
+               *lp++ = 0;
+               if (!endcmd(peekchar()))
+badtag:
+                       error("Bad tag|Give one tag per line");
+       } else if (lasttag[0] == 0)
+               error("No previous tag");
+       c = getchar();
+       if (!endcmd(c))
+               goto badtag;
+       if (c == EOF)
+               ungetchar(c);
+       clrstats();
+       do {
+               io = open(master ? "tags" : MASTERTAGS, 0);
+               if (master && io < 0)
+                       notagsfile = 1;
+               while (getfile() == 0) {
+                       register char *cp = linebuf;
+                       register char *lp = lasttag;
+                       char *oglobp;
+
+                       while (*cp && *lp == *cp)
+                               cp++, lp++;
+                       if (*lp || !iswhite(*cp))
+                               continue;
+                       close(io);
+                       while (*cp && iswhite(*cp))
+                               cp++;
+                       if (!*cp)
+badtags:
+                               serror("%s: Bad tags file entry", lasttag);
+                       lp = filebuf;
+                       while (*cp && *cp != ' ' && *cp != '\t') {
+                               if (lp < &filebuf[sizeof filebuf - 2])
+                                       *lp++ = *cp;
+                               cp++;
+                       }
+                       *lp++ = 0;
+                       if (*cp == 0)
+                               goto badtags;
+                       if (dol != zero) {
+                               /*
+                                * Save current position in 't for ^^ in visual.
+                                */
+                               names['t'-'a'] = *dot &~ 01;
+                               if (inopen) {
+                                       extern char *ncols['z'-'a'+1];
+                                       extern char *cursor;
+
+                                       ncols['t'-'a'] = cursor;
+                               }
+                       }
+                       strcpy(cmdbuf, cp);
+                       if (strcmp(filebuf, savedfile) || !edited) {
+                               char cmdbuf2[sizeof filebuf + 10];
+
+                               if (!quick) {
+                                       ckaw();
+                                       if (chng && dol > zero)
+                                               error("No write@since last change (:tag! overrides)");
+                               }
+                               oglobp = globp;
+                               strcpy(cmdbuf2, "e! ");
+                               strcat(cmdbuf2, filebuf);
+                               globp = cmdbuf2;
+                               d = peekc; ungetchar(0);
+                               /*
+                                * BUG: if it isn't found (user edited header
+                                * line) we get left in nomagic mode.
+                                */
+                               value(MAGIC) = 0;
+                               commands(1, 1);
+                               peekc = d;
+                               globp = oglobp;
+                               value(MAGIC) = omagic;
+                               samef = 0;
+                       }
+                       oglobp = globp;
+                       globp = cmdbuf;
+                       d = peekc; ungetchar(0);
+                       if (samef)
+                               markpr(dot);
+                       value(MAGIC) = 0;
+                       commands(1, 1);
+                       peekc = d;
+                       globp = oglobp;
+                       value(MAGIC) = omagic;
+                       return;
+               }
+       } while (++master == 0);
+       if (notagsfile)
+               error("No tags file");
+       serror("%s: No such tag@in tags file", lasttag);
+}
+
+/*
+ * Save lines from addr1 thru addr2 as though
+ * they had been deleted.
+ */
+yank()
+{
+
+       save12();
+       undkind = UNDNONE;
+       killcnt(addr2 - addr1 + 1);
+}
+
+/*
+ * z command; print windows of text in the file.
+ *
+ * If this seems unreasonably arcane, the reasons
+ * are historical.  This is one of the first commands
+ * added to the first ex (then called en) and the
+ * number of facilities here were the major advantage
+ * of en over ed since they allowed more use to be
+ * made of fast terminals w/o typing .,.22p all the time.
+ */
+bool   zhadpr;
+bool   znoclear;
+short  zweight;
+
+zop(hadpr)
+       int hadpr;
+{
+       register int c, lines, op;
+       bool excl;
+
+       zhadpr = hadpr;
+       notempty();
+       znoclear = 0;
+       zweight = 0;
+       excl = exclam();
+       switch (c = op = getchar()) {
+
+       case '^':
+               zweight = 1;
+       case '-':
+       case '+':
+               while (peekchar() == op) {
+                       ignchar();
+                       zweight++;
+               }
+       case '=':
+       case '.':
+               c = getchar();
+               break;
+
+       case EOF:
+               znoclear++;
+               break;
+
+       default:
+               op = 0;
+               break;
+       }
+       if (isdigit(c)) {
+               lines = c - '0';
+               for(;;) {
+                       c = getchar();
+                       if (!isdigit(c))
+                               break;
+                       lines *= 10;
+                       lines += c - '0';
+               }
+               if (lines < LINES)
+                       znoclear++;
+               value(WINDOW) = lines;
+               if (op == '=')
+                       lines += 2;
+       } else
+               lines = op == EOF ? value(SCROLL) : excl ? LINES - 1 : 2*value(SCROLL);
+       if (inopen || c != EOF) {
+               ungetchar(c);
+               newline();
+       }
+       addr1 = addr2;
+       if (addr2 == 0 && dot < dol && op == 0)
+               addr1 = addr2 = dot+1;
+       setdot();
+       zop2(lines, op);
+}
+
+zop2(lines, op)
+       register int lines;
+       register int op;
+{
+       register line *split;
+
+       split = NULL;
+       switch (op) {
+
+       case EOF:
+               if (addr2 == dol)
+                       error("\nAt EOF");
+       case '+':
+               if (addr2 == dol)
+                       error("At EOF");
+               addr2 += lines * zweight;
+               if (addr2 > dol)
+                       error("Hit BOTTOM");
+               addr2++;
+       default:
+               addr1 = addr2;
+               addr2 += lines-1;
+               dot = addr2;
+               break;
+
+       case '=':
+       case '.':
+               znoclear = 0;
+               lines--;
+               lines >>= 1;
+               if (op == '=')
+                       lines--;
+               addr1 = addr2 - lines;
+               if (op == '=')
+                       dot = split = addr2;
+               addr2 += lines;
+               if (op == '.') {
+                       markDOT();
+                       dot = addr2;
+               }
+               break;
+
+       case '^':
+       case '-':
+               addr2 -= lines * zweight;
+               if (addr2 < one)
+                       error("Hit TOP");
+               lines--;
+               addr1 = addr2 - lines;
+               dot = addr2;
+               break;
+       }
+       if (addr1 <= zero)
+               addr1 = one;
+       if (addr2 > dol)
+               addr2 = dol;
+       if (dot > dol)
+               dot = dol;
+       if (addr1 > addr2)
+               return;
+       if (op == EOF && zhadpr) {
+               getline(*addr1);
+               putchar('\r' | QUOTE);
+               shudclob = 1;
+       } else if (znoclear == 0 && CL != NOSTR && !inopen) {
+               flush1();
+               vclear();
+       }
+       if (addr2 - addr1 > 1)
+               pstart();
+       if (split) {
+               plines(addr1, split - 1, 0);
+               splitit();
+               plines(split, split, 0);
+               splitit();
+               addr1 = split + 1;
+       }
+       plines(addr1, addr2, 0);
+}
+
+static
+splitit()
+{
+       register int l;
+
+       for (l = COLUMNS > 80 ? 40 : COLUMNS / 2; l > 0; l--)
+               putchar('-');
+       putnl();
+}
+
+plines(adr1, adr2, movedot)
+       line *adr1;
+       register line *adr2;
+       bool movedot;
+{
+       register line *addr;
+
+       pofix();
+       for (addr = adr1; addr <= adr2; addr++) {
+               getline(*addr);
+               pline(lineno(addr));
+               if (inopen)
+                       putchar('\n' | QUOTE);
+               if (movedot)
+                       dot = addr;
+       }
+}
+
+pofix()
+{
+
+       if (inopen && Outchar != termchar) {
+               vnfl();
+               setoutt();
+       }
+}
+
+/*
+ * Dudley doright to the rescue.
+ * Undo saves the day again.
+ * A tip of the hatlo hat to Warren Teitleman
+ * who made undo as useful as do.
+ *
+ * Command level undo works easily because
+ * the editor has a unique temporary file
+ * index for every line which ever existed.
+ * We don't have to save large blocks of text,
+ * only the indices which are small.  We do this
+ * by moving them to after the last line in the
+ * line buffer array, and marking down info
+ * about whence they came.
+ *
+ * Undo is its own inverse.
+ */
+undo(c)
+       bool c;
+{
+       register int i;
+       register line *jp, *kp;
+       line *dolp1, *newdol, *newadot;
+
+       if (inglobal && inopen <= 0)
+               error("Can't undo in global@commands");
+       if (!c)
+               somechange();
+       pkill[0] = pkill[1] = 0;
+       change();
+       if (undkind == UNDMOVE) {
+               /*
+                * Command to be undone is a move command.
+                * This is handled as a special case by noting that
+                * a move "a,b m c" can be inverted by another move.
+                */
+               if ((i = (jp = unddel) - undap2) > 0) {
+                       /*
+                        * when c > b inverse is a+(c-b),c m a-1
+                        */
+                       addr2 = jp;
+                       addr1 = (jp = undap1) + i;
+                       unddel = jp-1;
+               } else {
+                       /*
+                        * when b > c inverse is  c+1,c+1+(b-a) m b
+                        */
+                       addr1 = ++jp;
+                       addr2 = jp + ((unddel = undap2) - undap1);
+               }
+               kp = undap1;
+               move1(0, unddel);
+               dot = kp;
+               Command = "move";
+               killed();
+       } else {
+               int cnt;
+
+               newadot = dot;
+               cnt = lineDOL();
+               newdol = dol;
+               dolp1 = dol + 1;
+               /*
+                * Command to be undone is a non-move.
+                * All such commands are treated as a combination of
+                * a delete command and a append command.
+                * We first move the lines appended by the last command
+                * from undap1 to undap2-1 so that they are just before the
+                * saved deleted lines.
+                */
+               if ((i = (kp = undap2) - (jp = undap1)) > 0) {
+                       if (kp != dolp1) {
+                               reverse(jp, kp);
+                               reverse(kp, dolp1);
+                               reverse(jp, dolp1);
+                       }
+                       /*
+                        * Account for possible backward motion of target
+                        * for restoration of saved deleted lines.
+                        */
+                       if (unddel >= jp)
+                               unddel -= i;
+                       newdol -= i;
+                       /*
+                        * For the case where no lines are restored, dot
+                        * is the line before the first line deleted.
+                        */
+                       dot = jp-1;
+               }
+               /*
+                * Now put the deleted lines, if any, back where they were.
+                * Basic operation is: dol+1,unddol m unddel
+                */
+               if (undkind == UNDPUT) {
+                       unddel = undap1 - 1;
+                       squish();
+               }
+               jp = unddel + 1;
+               if ((i = (kp = unddol) - dol) > 0) {
+                       if (jp != dolp1) {
+                               reverse(jp, dolp1);
+                               reverse(dolp1, ++kp);
+                               reverse(jp, kp);
+                       }
+                       /*
+                        * Account for possible forward motion of the target
+                        * for restoration of the deleted lines.
+                        */
+                       if (undap1 >= jp)
+                               undap1 += i;
+                       /*
+                        * Dot is the first resurrected line.
+                        */
+                       dot = jp;
+                       newdol += i;
+               }
+               /*
+                * Clean up so we are invertible
+                */
+               unddel = undap1 - 1;
+               undap1 = jp;
+               undap2 = jp + i;
+               dol = newdol;
+               netchHAD(cnt);
+               if (undkind == UNDALL) {
+                       dot = undadot;
+                       undadot = newadot;
+               }
+               undkind = UNDCHANGE;
+       }
+       if (dot == zero && dot != dol)
+               dot = one;
+}
+
+/*
+ * Be (almost completely) sure there really
+ * was a change, before claiming to undo.
+ */
+somechange()
+{
+       register line *ip, *jp;
+
+       switch (undkind) {
+
+       case UNDMOVE:
+               return;
+
+       case UNDCHANGE:
+               if (undap1 == undap2 && dol == unddol)
+                       break;
+               return;
+
+       case UNDPUT:
+               if (undap1 != undap2)
+                       return;
+               break;
+
+       case UNDALL:
+               if (unddol - dol != lineDOL())
+                       return;
+               for (ip = one, jp = dol + 1; ip <= dol; ip++, jp++)
+                       if ((*ip &~ 01) != (*jp &~ 01))
+                               return;
+               break;
+
+       case UNDNONE:
+               error("Nothing to undo");
+       }
+       error("Nothing changed|Last undoable command didn't change anything");
+}
+
+/*
+ * Map command:
+ * map src dest
+ */
+mapcmd(un)
+       int un; /* true if this is unmap command */
+{
+       char lhs[10], rhs[100]; /* max sizes resp. */
+       register char *p;
+       register char c;
+       char *dname;
+
+       if (skipend()) {
+               int i;
+
+               /* print current mapping values */
+               if (peekchar() != EOF)
+                       ignchar();
+               if (inopen)
+                       pofix();
+               for (i=0; arrows[i].mapto; i++)
+                       if (arrows[i].cap) {
+                               lprintf("%s", arrows[i].descr);
+                               putchar('\t');
+                               lprintf("%s", arrows[i].cap);
+                               putchar('\t');
+                               lprintf("%s", arrows[i].mapto);
+                               putNFL();
+                       }
+               return;
+       }
+
+       ignore(skipwh());
+       for (p=lhs; ; ) {
+               c = getchar();
+               if (c == CTRL(v)) {
+                       c = getchar();
+               } else if (any(c, " \t")) {
+                       if (un)
+                               eol();  /* will usually cause an error */
+                       else
+                               break;
+               } else if (endcmd(c)) {
+                       ungetchar(c);
+                       if (un) {
+                               newline();
+                               addmac(lhs, NOSTR, NOSTR);
+                               return;
+                       } else
+                               error("Missing rhs");
+               }
+               *p++ = c;
+       }
+       *p = 0;
+
+       if (skipend())
+               error("Missing rhs");
+       for (p=rhs; ; ) {
+               c = getchar();
+               if (c == CTRL(v)) {
+                       c = getchar();
+               } else if (endcmd(c)) {
+                       ungetchar(c);
+                       break;
+               }
+               *p++ = c;
+       }
+       *p = 0;
+       newline();
+       /*
+        * Special hack for function keys: #1 means key f1, etc.
+        * If the terminal doesn't have function keys, we just use #1.
+        */
+       if (lhs[0] == '#') {
+               char *fnkey;
+               char *fkey();
+               char funkey[3];
+
+               fnkey = fkey(lhs[1] - '0');
+               funkey[0] = 'f'; funkey[1] = lhs[1]; funkey[2] = 0;
+               if (fnkey)
+                       strcpy(lhs, fnkey);
+               dname = funkey;
+       } else {
+               dname = lhs;
+       }
+       addmac(lhs,rhs,dname);
+}
+
+/*
+ * Add a macro definition to those that already exist. The sequence of
+ * chars "src" is mapped into "dest". If src is already mapped into something
+ * this overrides the mapping. There is no recursion. Unmap is done by
+ * using NOSTR for dest.
+ */
+addmac(src,dest,dname)
+       register char *src, *dest, *dname;
+{
+       register int slot, zer;
+
+       if (dest) {
+               /* Make sure user doesn't screw himself */
+               /*
+                * Prevent tail recursion. We really should be
+                * checking to see if src is a suffix of dest
+                * but we are too lazy here, so we don't bother unless
+                * src is only 1 char long.
+                */
+               if (src[1] == 0 && src[0] == dest[strlen(dest)-1])
+                       error("No tail recursion");
+               /*
+                * We don't let the user rob himself of ":", and making
+                * multi char words is a bad idea so we don't allow it.
+                * Note that if user sets mapinput and maps all of return,
+                * linefeed, and escape, he can screw himself. This is
+                * so weird I don't bother to check for it.
+                */
+               if (isalpha(src[0]) && src[1] || any(src[0],":"))
+                       error("Too dangerous to map that");
+               /*
+                * If the src were null it would cause the dest to
+                * be mapped always forever. This is not good.
+                */
+               if (src[0] == 0)
+                       error("Null lhs");
+       }
+
+       /* see if we already have a def for src */
+       zer = -1;
+       for (slot=0; arrows[slot].mapto; slot++) {
+               if (arrows[slot].cap) {
+                       if (eq(src, arrows[slot].cap))
+                               break;  /* if so, reuse slot */
+               } else {
+                       zer = slot;     /* remember an empty slot */
+               }
+       }
+
+       if (dest == NOSTR) {
+               /* unmap */
+               if (arrows[slot].cap) {
+                       arrows[slot].cap = NOSTR;
+                       arrows[slot].descr = NOSTR;
+               } else {
+                       error("Not mapped|That macro wasn't mapped");
+               }
+               return;
+       }
+
+       /* reuse empty slot, if we found one and src isn't already defined */
+       if (zer >= 0 && arrows[slot].mapto == 0)
+               slot = zer;
+
+       /* if not, append to end */
+       if (slot >= MAXNOMACS)
+               error("Too many macros");
+       if (msnext == 0)        /* first time */
+               msnext = mapspace;
+       /* Check is a bit conservative, we charge for dname even if reusing src */
+       if (msnext - mapspace + strlen(dest) + strlen(src) + strlen(dname) + 3 > MAXCHARMACS)
+               error("Too much macro text");
+       CP(msnext, src);
+       arrows[slot].cap = msnext;
+       msnext += strlen(src) + 1;      /* plus 1 for null on the end */
+       CP(msnext, dest);
+       arrows[slot].mapto = msnext;
+       msnext += strlen(dest) + 1;
+       if (dname) {
+               CP(msnext, dname);
+               arrows[slot].descr = msnext;
+               msnext += strlen(dname) + 1;
+       } else {
+               /* default descr to string user enters */
+               arrows[slot].descr = src;
+       }
+}
+
+/*
+ * Implements macros from command mode. c is the buffer to
+ * get the macro from.
+ */
+cmdmac(c)
+char c;
+{
+       char macbuf[BUFSIZ];
+       line *ad, *a1, *a2;
+       char *oglobp;
+       char pk;
+       bool oinglobal;
+
+       lastmac = c;
+       oglobp = globp;
+       oinglobal = inglobal;
+       pk = peekc; peekc = 0;
+       if (inglobal < 2)
+               inglobal = 1;
+       regbuf(c, macbuf, sizeof(macbuf));
+       a1 = addr1; a2 = addr2;
+       for (ad=a1; ad<=a2; ad++) {
+               globp = macbuf;
+               dot = ad;
+               commands(1,1);
+       }
+       globp = oglobp;
+       inglobal = oinglobal;
+       peekc = pk;
+}
diff --git a/usr/src/cmd/ex/ex_data.c b/usr/src/cmd/ex/ex_data.c
new file mode 100644 (file)
index 0000000..402d208
--- /dev/null
@@ -0,0 +1,66 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+
+/*
+ * Initialization of option values.
+ * The option #defines in ex_vars.h are made
+ * from this file by the script makeoptions.
+ */
+char   direct[32] =
+       { '/', 't', 'm', 'p' };
+char   sections[32] = {
+       'N', 'H', 'S', 'H',                             /* -ms macros */
+       'H', ' ', 'H', 'U'                              /* -mm macros */
+};
+char   paragraphs[32] = {
+       'I', 'P', 'L', 'P', 'P', 'P', 'Q', 'P',         /* -ms macros */
+       'P', ' ', 'L', 'I',                             /* -mm macros */
+       'b', 'p'                                        /* bare nroff */
+};
+char   shell[32] =
+       { '/', 'b', 'i', 'n', '/', 's', 'h' };
+char   ttytype[16] =
+       { 'd', 'u', 'm', 'b' };
+
+short  COLUMNS = 80;
+short  LINES = 24;
+
+struct option options[NOPTS + 1] = {
+       "autoindent",   "ai",   ONOFF,          0,      0,      0,
+       "autoprint",    "ap",   ONOFF,          1,      1,      0,
+       "autowrite",    "aw",   ONOFF,          0,      0,      0,
+       "beautify",     "bf",   ONOFF,          0,      0,      0,
+       "directory",    "dir",  STRING,         0,      0,      direct,
+       "edcompatible", "ed",   ONOFF,          0,      0,      0,
+       "errorbells",   "eb",   ONOFF,          0,      0,      0,
+       "hardtabs",     "ht",   NUMERIC,        8,      8,      0,
+       "ignorecase",   "ic",   ONOFF,          0,      0,      0,
+       "lisp",         0,      ONOFF,          0,      0,      0,
+       "list",         0,      ONOFF,          0,      0,      0,
+       "magic",        0,      ONOFF,          1,      1,      0,
+       "mapinput",     "mi",   ONOFF,          0,      0,      0,
+       "number",       "nu",   ONOFF,          0,      0,      0,
+       "open",         0,      ONOFF,          1,      1,      0,
+       "optimize",     "opt",  ONOFF,          0,      0,      0,
+       "paragraphs",   "para", STRING,         0,      0,      paragraphs,
+       "prompt",       0,      ONOFF,          1,      1,      0,
+       "redraw",       0,      ONOFF,          0,      0,      0,
+       "report",       0,      NUMERIC,        5,      5,      0,
+       "scroll",       "scr",  NUMERIC,        12,     12,     0,
+       "sections",     "sect", STRING,         0,      0,      sections,
+       "shell",        "sh",   STRING,         0,      0,      shell,
+       "shiftwidth",   "sw",   NUMERIC,        TABS,   TABS,   0,
+       "showmatch",    "sm",   ONOFF,          0,      0,      0,
+       "slowopen",     "slow", ONOFF,          0,      0,      0,
+       "tabstop",      "ts",   NUMERIC,        TABS,   TABS,   0,
+       "ttytype",      "tty",  OTERM,          0,      0,      ttytype,
+       "term",         0,      OTERM,          0,      0,      ttytype,
+       "terse",        0,      ONOFF,          0,      0,      0,
+       "warn",         0,      ONOFF,          1,      1,      0,
+       "window",       "wi",   NUMERIC,        23,     23,     0,
+       "wrapscan",     "ws",   ONOFF,          1,      1,      0,
+       "wrapmargin",   "wm",   NUMERIC,        0,      0,      0,
+       "writeany",     "wa",   ONOFF,          0,      0,      0,
+       0,              0,      0,              0,      0,      0,
+};
diff --git a/usr/src/cmd/ex/ex_get.c b/usr/src/cmd/ex/ex_get.c
new file mode 100644 (file)
index 0000000..4d19535
--- /dev/null
@@ -0,0 +1,269 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+
+/*
+ * Input routines for command mode.
+ * Since we translate the end of reads into the implied ^D's
+ * we have different flavors of routines which do/don't return such.
+ */
+static bool junkbs;
+short  lastc = '\n';
+
+ignchar()
+{
+       register int c;
+
+       do
+               c = getcd();
+       while (c == CTRL(d));
+}
+
+getchar()
+{
+       register int c;
+
+       do
+               c = getcd();
+       while (c == CTRL(d));
+       return (c);
+}
+
+getcd()
+{
+       register int c;
+
+again:
+       c = getach();
+       if (c == EOF)
+               return (c);
+       c &= TRIM;
+       if (!inopen)
+               if (c == CTRL(d))
+                       setlastchar('\n');
+               else if (junk(c)) {
+                       checkjunk(c);
+                       goto again;
+               }
+       return (c);
+}
+
+peekchar()
+{
+
+       if (peekc == 0)
+               peekc = getchar();
+       return (peekc);
+}
+
+peekcd()
+{
+
+       if (peekc == 0)
+               peekc = getcd();
+       return (peekc);
+}
+
+getach()
+{
+       register int c;
+       static char inline[128];
+
+       c = peekc;
+       if (c != 0) {
+               peekc = 0;
+               return (c);
+       }
+       if (globp) {
+               if (*globp)
+                       return (*globp++);
+               globp = 0;
+               return (lastc = EOF);
+       }
+top:
+       if (input) {
+               if (c = *input++) {
+                       if (c &= TRIM)
+                               return (lastc = c);
+                       goto top;
+               }
+               input = 0;
+       }
+       flush();
+       if (intty) {
+               c = read(0, inline, sizeof inline - 4);
+               if (c < 0)
+                       return (lastc = EOF);
+               if (c == 0 || inline[c-1] != '\n')
+                       inline[c++] = CTRL(d);
+               if (inline[c-1] == '\n')
+                       noteinp();
+               inline[c] = 0;
+               for (c--; c >= 0; c--)
+                       if (inline[c] == 0)
+                               inline[c] = QUOTE;
+               input = inline;
+               goto top;
+       }
+       if (read(0, (char *) &lastc, 1) != 1)
+               lastc = EOF;
+       return (lastc);
+}
+
+/*
+ * Input routine for insert/append/change in command mode.
+ * Most work here is in handling autoindent.
+ */
+static short   lastin;
+
+gettty()
+{
+       register int c = 0;
+       register char *cp = genbuf;
+       char hadup = 0;
+       int numbline();
+       extern int (*Pline)();
+       int offset = Pline == numbline ? 8 : 0;
+       int ch;
+
+       if (intty && !inglobal) {
+               if (offset) {
+                       holdcm = 1;
+                       printf("  %4d  ", lineDOT() + 1);
+                       flush();
+                       holdcm = 0;
+               }
+               if (value(AUTOINDENT) ^ aiflag) {
+                       holdcm = 1;
+#ifdef LISPCODE
+                       if (value(LISP))
+                               lastin = lindent(dot + 1);
+#endif
+                       tab(lastin + offset);
+                       while ((c = getcd()) == CTRL(d)) {
+                               if (lastin == 0 && isatty(0) == -1) {
+                                       holdcm = 0;
+                                       return (EOF);
+                               }
+                               lastin = backtab(lastin);
+                               tab(lastin + offset);
+                       }
+                       switch (c) {
+
+                       case '^':
+                       case '0':
+                               ch = getcd();
+                               if (ch == CTRL(d)) {
+                                       if (c == '0')
+                                               lastin = 0;
+                                       if (!OS) {
+                                               putchar('\b' | QUOTE);
+                                               putchar(' ' | QUOTE);
+                                               putchar('\b' | QUOTE);
+                                       }
+                                       tab(offset);
+                                       hadup = 1;
+                                       c = getchar();
+                               } else
+                                       ungetchar(ch);
+                               break;
+
+                       case '.':
+                               if (peekchar() == '\n') {
+                                       ignchar();
+                                       noteinp();
+                                       holdcm = 0;
+                                       return (EOF);
+                               }
+                               break;
+
+                       case '\n':
+                               hadup = 1;
+                               break;
+                       }
+               }
+               flush();
+               holdcm = 0;
+       }
+       if (c == 0)
+               c = getchar();
+       while (c != EOF && c != '\n') {
+               if (cp > &genbuf[LBSIZE - 2])
+                       error("Input line too long");
+               *cp++ = c;
+               c = getchar();
+       }
+       if (c == EOF) {
+               if (inglobal)
+                       ungetchar(EOF);
+               return (EOF);
+       }
+       *cp = 0;
+       cp = linebuf;
+       if ((value(AUTOINDENT) ^ aiflag) && hadup == 0 && intty && !inglobal) {
+               lastin = c = smunch(lastin, genbuf);
+               for (c = lastin; c >= value(TABSTOP); c -= value(TABSTOP))
+                       *cp++ = '\t';
+               for (; c > 0; c--)
+                       *cp++ = ' ';
+       }
+       CP(cp, genbuf);
+       if (linebuf[0] == '.' && linebuf[1] == 0)
+               return (EOF);
+       return (0);
+}
+
+/*
+ * Crunch the indent.
+ * Hard thing here is that in command mode some of the indent
+ * is only implicit, so we must seed the column counter.
+ * This should really be done differently so as to use the whitecnt routine
+ * and also to hack indenting for LISP.
+ */
+smunch(col, ocp)
+       register int col;
+       char *ocp;
+{
+       register char *cp;
+
+       cp = ocp;
+       for (;;)
+               switch (*cp++) {
+
+               case ' ':
+                       col++;
+                       continue;
+
+               case '\t':
+                       col += value(TABSTOP) - (col % value(TABSTOP));
+                       continue;
+
+               default:
+                       cp--;
+                       CP(ocp, cp);
+                       return (col);
+               }
+}
+
+char   *cntrlhm =      "^H discarded\n";
+
+checkjunk(c)
+       char c;
+{
+
+       if (junkbs == 0 && c == '\b') {
+               write(2, cntrlhm, 13);
+               junkbs = 1;
+       }
+}
+
+line *
+setin(addr)
+       line *addr;
+{
+
+       if (addr == zero)
+               lastin = 0;
+       else
+               getline(*addr), lastin = smunch(0, linebuf);
+}
diff --git a/usr/src/cmd/ex/ex_io.c b/usr/src/cmd/ex/ex_io.c
new file mode 100644 (file)
index 0000000..d22f82e
--- /dev/null
@@ -0,0 +1,1047 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_argv.h"
+#include "ex_temp.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * File input/output, unix escapes, source, filtering preserve and recover
+ */
+
+/*
+ * Following remember where . was in the previous file for return
+ * on file switching.
+ */
+int    altdot;
+int    oldadot;
+bool   wasalt;
+
+long   cntch;                  /* Count of characters on unit io */
+#ifndef VMUNIX
+short  cntln;                  /* Count of lines " */
+#else
+int    cntln;
+#endif
+long   cntnull;                /* Count of nulls " */
+long   cntodd;                 /* Count of non-ascii characters " */
+
+/*
+ * Parse file name for command encoded by comm.
+ * If comm is E then command is doomed and we are
+ * parsing just so user won't have to retype the name.
+ */
+filename(comm)
+       int comm;
+{
+       register int c = comm, d;
+       register int i;
+
+       d = getchar();
+       if (endcmd(d)) {
+               if (savedfile[0] == 0 && comm != 'f')
+                       error("No file|No current filename");
+               CP(file, savedfile);
+               wasalt = 0;
+               oldadot = altdot;
+               if (d == EOF)
+                       ungetchar(d);
+       } else {
+               ungetchar(d);
+               getone();
+               eol();
+               if (savedfile[0] == 0 && c != 'E' && c != 'e') {
+                       c = 'e';
+                       edited = 0;
+               }
+               wasalt = strcmp(file, altfile) == 0;
+               oldadot = altdot;
+               switch (c) {
+
+               case 'f':
+                       edited = 0;
+                       /* fall into ... */
+
+               case 'e':
+                       if (savedfile[0]) {
+                               altdot = lineDOT();
+                               CP(altfile, savedfile);
+                       }
+                       CP(savedfile, file);
+                       break;
+
+               default:
+                       if (file[0]) {
+                               if (c != 'E')
+                                       altdot = lineDOT();
+                               CP(altfile, file);
+                       }
+                       break;
+               }
+       }
+       if (hush && comm != 'f' || comm == 'E')
+               return;
+       if (file[0] != 0) {
+               lprintf("\"%s\"", file);
+               if (comm == 'f') {
+                       if (!edited)
+                               printf(" [Not edited]");
+                       if (tchng)
+                               printf(" [Modified]");
+               }
+               flush();
+       } else
+               printf("No file ");
+       if (comm == 'f') {
+               if (!(i = lineDOL()))
+                       i++;
+               printf(" line %d of %d --%ld%%--", lineDOT(), lineDOL(),
+                   (long) 100 * lineDOT() / i);
+       }
+}
+
+/*
+ * Get the argument words for a command into genbuf
+ * expanding # and %.
+ */
+getargs()
+{
+       register int c;
+       register char *cp, *fp;
+       static char fpatbuf[32];        /* hence limit on :next +/pat */
+
+       pastwh();
+       if (peekchar() == '+') {
+               for (cp = fpatbuf;;) {
+                       c = *cp++ = getchar();
+                       if (cp >= &fpatbuf[sizeof(fpatbuf)])
+                               error("Pattern too long");
+                       if (c == '\\' && isspace(peekchar()))
+                               c = getchar();
+                       if (c == EOF || isspace(c)) {
+                               ungetchar(c);
+                               *--cp = 0;
+                               firstpat = &fpatbuf[1];
+                               break;
+                       }
+               }
+       }
+       if (skipend())
+               return (0);
+       CP(genbuf, "echo "); cp = &genbuf[5];
+       for (;;) {
+               c = getchar();
+               if (endcmd(c)) {
+                       ungetchar(c);
+                       break;
+               }
+               switch (c) {
+
+               case '\\':
+                       if (any(peekchar(), "#%"))
+                               c = getchar();
+                       /* fall into... */
+
+               default:
+                       if (cp > &genbuf[LBSIZE - 2])
+flong:
+                               error("Argument buffer overflow");
+                       *cp++ = c;
+                       break;
+
+               case '#':
+                       fp = altfile;
+                       if (*fp == 0)
+                               error("No alternate filename@to substitute for #");
+                       goto filexp;
+
+               case '%':
+                       fp = savedfile;
+                       if (*fp == 0)
+                               error("No current filename@to substitute for %%");
+filexp:
+                       while (*fp) {
+                               if (cp > &genbuf[LBSIZE - 2])
+                                       goto flong;
+                               *cp++ = *fp++;
+                       }
+                       break;
+               }
+       }
+       *cp = 0;
+       return (1);
+}
+
+/*
+ * Glob the argument words in genbuf, or if no globbing
+ * is implied, just split them up directly.
+ */
+glob(gp)
+       struct glob *gp;
+{
+       int pvec[2];
+       register char **argv = gp->argv;
+       register char *cp = gp->argspac;
+       register int c;
+       char ch;
+       int nleft = NCARGS;
+
+       gp->argc0 = 0;
+       if (gscan() == 0) {
+               register char *v = genbuf + 5;          /* strlen("echo ") */
+
+               for (;;) {
+                       while (isspace(*v))
+                               v++;
+                       if (!*v)
+                               break;
+                       *argv++ = cp;
+                       while (*v && !isspace(*v))
+                               *cp++ = *v++;
+                       *cp++ = 0;
+                       gp->argc0++;
+               }
+               *argv = 0;
+               return;
+       }
+       if (pipe(pvec) < 0)
+               error("Can't make pipe to glob");
+       pid = fork();
+       io = pvec[0];
+       if (pid < 0) {
+               close(pvec[1]);
+               error("Can't fork to do glob");
+       }
+       if (pid == 0) {
+               int oerrno;
+
+               close(1);
+               dup(pvec[1]);
+               close(pvec[0]);
+               execl(svalue(SHELL), "sh", "-c", genbuf, 0);
+               oerrno = errno; close(1); dup(2); errno = oerrno;
+               filioerr(svalue(SHELL));
+       }
+       close(pvec[1]);
+       do {
+               *argv = cp;
+               for (;;) {
+                       if (read(io, &ch, 1) != 1) {
+                               close(io);
+                               c = -1;
+                       } else
+                               c = ch & TRIM;
+                       if (c <= 0 || isspace(c))
+                               break;
+                       *cp++ = c;
+                       if (--nleft <= 0)
+                               error("Arg list too long");
+               }
+               if (cp != *argv) {
+                       --nleft;
+                       *cp++ = 0;
+                       gp->argc0++;
+                       if (gp->argc0 >= NARGS)
+                               error("Arg list too long");
+                       argv++;
+               }
+       } while (c >= 0);
+       waitfor();
+       if (gp->argc0 == 0)
+               error(NOSTR);
+}
+
+/*
+ * Scan genbuf for shell metacharacters.
+ * Set is union of v7 shell and csh metas.
+ */
+gscan()
+{
+       register char *cp;
+
+       for (cp = genbuf; *cp; cp++)
+               if (any(*cp, "~{[*?$`'\"\\"))
+                       return (1);
+       return (0);
+}
+
+/*
+ * Parse one filename into file.
+ */
+getone()
+{
+       register char *str;
+       struct glob G;
+
+       if (getargs() == 0)
+               error("Missing filename");
+       glob(&G);
+       if (G.argc0 > 1)
+               error("Ambiguous|Too many file names");
+       str = G.argv[G.argc0 - 1];
+       if (strlen(str) > FNSIZE - 4)
+               error("Filename too long");
+samef:
+       CP(file, str);
+}
+
+/*
+ * Read a file from the world.
+ * C is command, 'e' if this really an edit (or a recover).
+ */
+rop(c)
+       int c;
+{
+       register int i;
+       struct stat stbuf;
+       short magic;
+
+       io = open(file, 0);
+       if (io < 0) {
+               if (c == 'e' && errno == ENOENT)
+                       edited++;
+               syserror();
+       }
+       if (fstat(io, &stbuf))
+               syserror();
+       switch (stbuf.st_mode & S_IFMT) {
+
+       case S_IFBLK:
+               error(" Block special file");
+
+       case S_IFCHR:
+               if (isatty(io))
+                       error(" Teletype");
+               if (samei(&stbuf, "/dev/null"))
+                       break;
+               error(" Character special file");
+
+       case S_IFDIR:
+               error(" Directory");
+
+       case S_IFREG:
+               i = read(io, (char *) &magic, sizeof(magic));
+               lseek(io, 0l, 0);
+               if (i != sizeof(magic))
+                       break;
+               switch (magic) {
+
+               case 0405:
+               case 0407:
+               case 0410:
+               case 0411:
+                       error(" Executable");
+
+               case 0177545:
+               case 0177555:
+                       error(" Archive");
+
+               default:
+                       if (magic & 0100200)
+                               error(" Non-ascii file");
+                       break;
+               }
+       }
+       if (c == 'r')
+               setdot();
+       else
+               setall();
+       if (inopen && c == 'r')
+               undap1 = undap2 = dot + 1;
+       rop2();
+       rop3(c);
+}
+
+rop2()
+{
+
+       deletenone();
+       clrstats();
+       ignore(append(getfile, addr2));
+}
+
+rop3(c)
+       int c;
+{
+
+       if (iostats() == 0 && c == 'e')
+               edited++;
+       if (c == 'e') {
+               if (wasalt || firstpat) {
+                       register line *addr = zero + oldadot;
+
+                       if (addr > dol)
+                               addr = dol;
+                       if (firstpat) {
+                               globp = (*firstpat) ? firstpat : "$";
+                               commands(1,1);
+                               firstpat = 0;
+                       } else if (addr >= one) {
+                               if (inopen)
+                                       dot = addr;
+                               markpr(addr);
+                       } else
+                               goto other;
+               } else
+other:
+                       if (dol > zero) {
+                               if (inopen)
+                                       dot = one;
+                               markpr(one);
+                       }
+               undkind = UNDNONE;
+               if (inopen) {
+                       vcline = 0;
+                       vreplace(0, LINES, lineDOL());
+               }
+       }
+       if (laste) {
+               laste = 0;
+               sync();
+       }
+}
+
+/*
+ * Are these two really the same inode?
+ */
+samei(sp, cp)
+       struct stat *sp;
+       char *cp;
+{
+       struct stat stb;
+
+       if (stat(cp, &stb) < 0 || sp->st_dev != stb.st_dev)
+               return (0);
+       return (sp->st_ino == stb.st_ino);
+}
+
+/* Returns from edited() */
+#define        EDF     0               /* Edited file */
+#define        NOTEDF  -1              /* Not edited file */
+#define        PARTBUF 1               /* Write of partial buffer to Edited file */
+
+/*
+ * Write a file.
+ */
+wop(dofname)
+bool dofname;  /* if 1 call filename, else use savedfile */
+{
+       register int c, exclam, nonexist;
+       line *saddr1, *saddr2;
+       struct stat stbuf;
+
+       c = 0;
+       exclam = 0;
+       if (dofname) {
+               if (peekchar() == '!')
+                       exclam++, ignchar();
+               ignore(skipwh());
+               while (peekchar() == '>')
+                       ignchar(), c++, ignore(skipwh());
+               if (c != 0 && c != 2)
+                       error("Write forms are 'w' and 'w>>'");
+               filename('w');
+       } else {
+               saddr1=addr1;
+               saddr2=addr2;
+               addr1=one;
+               addr2=dol;
+               CP(file, savedfile);
+               if (inopen) {
+                       vclrech(0);
+                       splitw++;
+               }
+               lprintf("\"%s\"", file);
+       }
+       nonexist = stat(file, &stbuf);
+       switch (c) {
+
+       case 0:
+               if (!exclam && !value(WRITEANY)) switch (edfile()) {
+               
+               case NOTEDF:
+                       if (nonexist)
+                               break;
+                       if ((stbuf.st_mode & S_IFMT) == S_IFCHR) {
+                               if (samei(&stbuf, "/dev/null"))
+                                       break;
+                               if (samei(&stbuf, "/dev/tty"))
+                                       break;
+                       }
+                       io = open(file, 1);
+                       if (io < 0)
+                               syserror();
+                       if (!isatty(io))
+                               serror(" File exists| File exists - use \"w! %s\" to overwrite", file);
+                       close(io);
+                       break;
+
+               case PARTBUF:
+                       error(" Use \"w!\" to write partial buffer");
+               }
+cre:
+/*
+               synctmp();
+*/
+#ifdef V6
+               io = creat(file, 0644);
+#else
+               io = creat(file, 0666);
+#endif
+               if (io < 0)
+                       syserror();
+               if (hush == 0)
+                       if (nonexist)
+                               printf(" [New file]");
+                       else if (value(WRITEANY) && edfile() != EDF)
+                               printf(" [Existing file]");
+               break;
+
+       case 2:
+               io = open(file, 1);
+               if (io < 0) {
+                       if (exclam || value(WRITEANY))
+                               goto cre;
+                       syserror();
+               }
+               lseek(io, 0l, 2);
+               break;
+       }
+       putfile();
+       ignore(iostats());
+       if (c != 2 && addr1 == one && addr2 == dol) {
+               if (eq(file, savedfile))
+                       edited = 1;
+               sync();
+       }
+       if (!dofname) {
+               addr1 = saddr1;
+               addr2 = saddr2;
+       }
+}
+
+/*
+ * Is file the edited file?
+ * Work here is that it is not considered edited
+ * if this is a partial buffer, and distinguish
+ * all cases.
+ */
+edfile()
+{
+
+       if (!edited || !eq(file, savedfile))
+               return (NOTEDF);
+       return (addr1 == one && addr2 == dol ? EDF : PARTBUF);
+}
+
+/*
+ * First part of a shell escape,
+ * parse the line, expanding # and % and ! and printing if implied.
+ */
+unix0(warn)
+       bool warn;
+{
+       register char *up, *fp;
+       register short c;
+       char printub, puxb[UXBSIZE + sizeof (int)];
+
+       printub = 0;
+       CP(puxb, uxb);
+       c = getchar();
+       if (c == '\n' || c == EOF)
+               error("Incomplete shell escape command@- use 'shell' to get a shell");
+       up = uxb;
+       do {
+               switch (c) {
+
+               case '\\':
+                       if (any(peekchar(), "%#!"))
+                               c = getchar();
+               default:
+                       if (up >= &uxb[UXBSIZE]) {
+tunix:
+                               uxb[0] = 0;
+                               error("Command too long");
+                       }
+                       *up++ = c;
+                       break;
+
+               case '!':
+                       fp = puxb;
+                       if (*fp == 0) {
+                               uxb[0] = 0;
+                               error("No previous command@to substitute for !");
+                       }
+                       printub++;
+                       while (*fp) {
+                               if (up >= &uxb[UXBSIZE])
+                                       goto tunix;
+                               *up++ = *fp++;
+                       }
+                       break;
+
+               case '#':
+                       fp = altfile;
+                       if (*fp == 0) {
+                               uxb[0] = 0;
+                               error("No alternate filename@to substitute for #");
+                       }
+                       goto uexp;
+
+               case '%':
+                       fp = savedfile;
+                       if (*fp == 0) {
+                               uxb[0] = 0;
+                               error("No filename@to substitute for %%");
+                       }
+uexp:
+                       printub++;
+                       while (*fp) {
+                               if (up >= &uxb[UXBSIZE])
+                                       goto tunix;
+                               *up++ = *fp++ | QUOTE;
+                       }
+                       break;
+               }
+               c = getchar();
+       } while (c == '|' || !endcmd(c));
+       if (c == EOF)
+               ungetchar(c);
+       *up = 0;
+       if (!inopen)
+               resetflav();
+       if (warn)
+               ckaw();
+       if (warn && hush == 0 && chng && xchng != chng && value(WARN) && dol > zero) {
+               xchng = chng;
+               vnfl();
+               printf(mesg("[No write]|[No write since last change]"));
+               noonl();
+               flush();
+       } else
+               warn = 0;
+       if (printub) {
+               if (uxb[0] == 0)
+                       error("No previous command@to repeat");
+               if (inopen) {
+                       splitw++;
+                       vclean();
+                       vgoto(WECHO, 0);
+               }
+               if (warn)
+                       vnfl();
+               if (hush == 0)
+                       lprintf("!%s", uxb);
+               if (inopen) {
+                       vclreol();
+                       vgoto(WECHO, 0);
+               } else
+                       putnl();
+               flush();
+       }
+}
+
+/*
+ * Do the real work for execution of a shell escape.
+ * Mode is like the number passed to open system calls
+ * and indicates filtering.  If input is implied, newstdin
+ * must have been setup already.
+ */
+unixex(opt, up, newstdin, mode)
+       char *opt, *up;
+       int newstdin, mode;
+{
+       int pvec[2], f;
+
+       signal(SIGINT, SIG_IGN);
+       if (inopen)
+               f = setty(normf);
+       if ((mode & 1) && pipe(pvec) < 0) {
+               /* Newstdin should be io so it will be closed */
+               if (inopen)
+                       setty(f);
+               error("Can't make pipe for filter");
+       }
+#ifndef VFORK
+       pid = fork();
+#else
+       pid = vfork();
+#endif
+       if (pid < 0) {
+               if (mode & 1) {
+                       close(pvec[0]);
+                       close(pvec[1]);
+               }
+               setrupt();
+               error("No more processes");
+       }
+       if (pid == 0) {
+               if (mode & 2) {
+                       close(0);
+                       dup(newstdin);
+                       close(newstdin);
+               }
+               if (mode & 1) {
+                       close(pvec[0]);
+                       close(1);
+                       dup(pvec[1]);
+                       if (inopen) {
+                               close(2);
+                               dup(1);
+                       }
+                       close(pvec[1]);
+               }
+               if (io)
+                       close(io);
+               if (tfile)
+                       close(tfile);
+#ifndef VMUNIX
+               close(erfile);
+#endif
+               signal(SIGHUP, oldhup);
+               signal(SIGQUIT, oldquit);
+               if (ruptible)
+                       signal(SIGINT, SIG_DFL);
+               execl(svalue(SHELL), "sh", opt, up, (char *) 0);
+               printf("No %s!\n", svalue(SHELL));
+               error(NOSTR);
+       }
+       if (mode & 1) {
+               io = pvec[0];
+               close(pvec[1]);
+       }
+       if (newstdin)
+               close(newstdin);
+       return (f);
+}
+
+/*
+ * Wait for the command to complete.
+ * F is for restoration of tty mode if from open/visual.
+ * C flags suppression of printing.
+ */
+unixwt(c, f)
+       bool c;
+       int f;
+{
+
+       waitfor();
+       if (inopen)
+               setty(f);
+       setrupt();
+       if (!inopen && c && hush == 0) {
+               printf("!\n");
+               flush();
+               termreset();
+               gettmode();
+       }
+}
+
+/*
+ * Setup a pipeline for the filtration implied by mode
+ * which is like a open number.  If input is required to
+ * the filter, then a child editor is created to write it.
+ * If output is catch it from io which is created by unixex.
+ */
+filter(mode)
+       register int mode;
+{
+       static int pvec[2];
+       register int f;
+       register int lines = lineDOL();
+
+       mode++;
+       if (mode & 2) {
+               signal(SIGINT, SIG_IGN);
+               if (pipe(pvec) < 0)
+                       error("Can't make pipe");
+               pid = fork();
+               io = pvec[0];
+               if (pid < 0) {
+                       setrupt();
+                       close(pvec[1]);
+                       error("No more processes");
+               }
+               if (pid == 0) {
+                       setrupt();
+                       io = pvec[1];
+                       close(pvec[0]);
+                       putfile();
+                       exit(0);
+               }
+               close(pvec[1]);
+               io = pvec[0];
+               setrupt();
+       }
+       f = unixex("-c", uxb, (mode & 2) ? pvec[0] : 0, mode);
+       if (mode == 3) {
+               delete(0);
+               addr2 = addr1 - 1;
+       }
+       if (mode & 1) {
+               undap1 = undap2 = addr2+1;
+               ignore(append(getfile, addr2));
+       }
+       close(io);
+       io = -1;
+       unixwt(!inopen, f);
+       netchHAD(lines);
+}
+
+/*
+ * Set up to do a recover, getting io to be a pipe from
+ * the recover process.
+ */
+recover()
+{
+       static int pvec[2];
+
+       if (pipe(pvec) < 0)
+               error(" Can't make pipe for recovery");
+       pid = fork();
+       io = pvec[0];
+       if (pid < 0) {
+               close(pvec[1]);
+               error(" Can't fork to execute recovery");
+       }
+       if (pid == 0) {
+               close(2);
+               dup(1);
+               close(1);
+               dup(pvec[1]);
+               close(pvec[1]);
+               execl(EXRECOVER, "exrecover", svalue(DIRECTORY), file, (char *) 0);
+               close(1);
+               dup(2);
+               error(" No recovery routine");
+       }
+       close(pvec[1]);
+}
+
+/*
+ * Wait for the process (pid an external) to complete.
+ */
+waitfor()
+{
+
+       do
+               rpid = wait(&status);
+       while (rpid != pid && rpid != -1);
+       status = (status >> 8) & 0377;
+}
+
+/*
+ * The end of a recover operation.  If the process
+ * exits non-zero, force not edited; otherwise force
+ * a write.
+ */
+revocer()
+{
+
+       waitfor();
+       if (pid == rpid && status != 0)
+               edited = 0;
+       else
+               change();
+}
+
+/*
+ * Extract the next line from the io stream.
+ */
+static char *nextip;
+
+getfile()
+{
+       register short c;
+       register char *lp, *fp;
+
+       lp = linebuf;
+       fp = nextip;
+       do {
+               if (--ninbuf < 0) {
+                       ninbuf = read(io, genbuf, LBSIZE) - 1;
+                       if (ninbuf < 0) {
+                               if (lp != linebuf) {
+                                       printf(" [Incomplete last line]");
+                                       break;
+                               }
+                               return (EOF);
+                       }
+                       fp = genbuf;
+               }
+               if (lp >= &linebuf[LBSIZE]) {
+                       error(" Line too long");
+               }
+               c = *fp++;
+               if (c == 0) {
+                       cntnull++;
+                       continue;
+               }
+               if (c & QUOTE) {
+                       cntodd++;
+                       c &= TRIM;
+                       if (c == 0)
+                               continue;
+               }
+               *lp++ = c;
+       } while (c != '\n');
+       cntch += lp - linebuf;
+       *--lp = 0;
+       nextip = fp;
+       cntln++;
+       return (0);
+}
+
+/*
+ * Write a range onto the io stream.
+ */
+putfile()
+{
+       line *a1;
+       register char *fp, *lp;
+       register int nib;
+
+       a1 = addr1;
+       clrstats();
+       cntln = addr2 - a1 + 1;
+       if (cntln == 0)
+               return;
+       nib = BUFSIZ;
+       fp = genbuf;
+       do {
+               getline(*a1++);
+               lp = linebuf;
+               for (;;) {
+                       if (--nib < 0) {
+                               nib = fp - genbuf;
+                               if (write(io, genbuf, nib) != nib) {
+                                       wrerror();
+                               }
+                               cntch += nib;
+                               nib = BUFSIZ - 1;
+                               fp = genbuf;
+                       }
+                       if ((*fp++ = *lp++) == 0) {
+                               fp[-1] = '\n';
+                               break;
+                       }
+               }
+       } while (a1 <= addr2);
+       nib = fp - genbuf;
+       if (write(io, genbuf, nib) != nib) {
+               wrerror();
+       }
+       cntch += nib;
+}
+
+/*
+ * A write error has occurred;  if the file being written was
+ * the edited file then we consider it to have changed since it is
+ * now likely scrambled.
+ */
+wrerror()
+{
+
+       if (eq(file, savedfile) && edited)
+               change();
+       syserror();
+}
+
+/*
+ * Source command, handles nested sources.
+ * Traps errors since it mungs unit 0 during the source.
+ */
+static short slevel;
+
+source(fil, okfail)
+       char *fil;
+       bool okfail;
+{
+       jmp_buf osetexit;
+       register int saveinp, ointty, oerrno;
+       int oprompt;
+
+       signal(SIGINT, SIG_IGN);
+       saveinp = dup(0);
+       if (saveinp < 0)
+               error("Too many nested sources");
+       close(0);
+       if (open(fil, 0) < 0) {
+               oerrno = errno;
+               setrupt();
+               dup(saveinp);
+               close(saveinp);
+               errno = oerrno;
+               if (!okfail)
+                       filioerr(fil);
+               return;
+       }
+       slevel++;
+       ointty = intty;
+       intty = isatty(0);
+       oprompt = value(PROMPT);
+       value(PROMPT) &= intty;
+       getexit(osetexit);
+       setrupt();
+       if (setexit() == 0)
+               commands(1, 1);
+       else if (slevel > 1) {
+               close(0);
+               dup(saveinp);
+               close(saveinp);
+               slevel--;
+               resexit(osetexit);
+               reset();
+       }
+       intty = ointty;
+       value(PROMPT) = oprompt;
+       close(0);
+       dup(saveinp);
+       close(saveinp);
+       slevel--;
+       resexit(osetexit);
+}
+
+/*
+ * Clear io statistics before a read or write.
+ */
+clrstats()
+{
+
+       ninbuf = 0;
+       cntch = 0;
+       cntln = 0;
+       cntnull = 0;
+       cntodd = 0;
+}
+
+/*
+ * Io is finished, close the unit and print statistics.
+ */
+iostats()
+{
+
+       close(io);
+       io = -1;
+       if (hush == 0) {
+               if (value(TERSE))
+                       printf(" %d/%D", cntln, cntch);
+               else
+                       printf(" %d line%s, %D character%s", cntln, plural((long) cntln),
+                           cntch, plural(cntch));
+               if (cntnull || cntodd) {
+                       printf(" (");
+                       if (cntnull) {
+                               printf("%D null", cntnull);
+                               if (cntodd)
+                                       printf(", ");
+                       }
+                       if (cntodd)
+                               printf("%D non-ASCII", cntodd);
+                       putchar(')');
+               }
+               noonl();
+               flush();
+       }
+       return (cntnull != 0 || cntodd != 0);
+}
diff --git a/usr/src/cmd/ex/ex_put.c b/usr/src/cmd/ex/ex_put.c
new file mode 100644 (file)
index 0000000..c184d88
--- /dev/null
@@ -0,0 +1,888 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * Terminal driving and line formatting routines.
+ * Basic motion optimizations are done here as well
+ * as formatting of lines (printing of control characters,
+ * line numbering and the like).
+ */
+
+/*
+ * The routines outchar, putchar and pline are actually
+ * variables, and these variables point at the current definitions
+ * of the routines.  See the routine setflav.
+ * We sometimes make outchar be routines which catch the characters
+ * to be printed, e.g. if we want to see how long a line is.
+ * During open/visual, outchar and putchar will be set to
+ * routines in the file ex_vput.c (vputchar, vinschar, etc.).
+ */
+int    (*Outchar)() = termchar;
+int    (*Putchar)() = normchar;
+int    (*Pline)() = normline;
+
+int (*
+setlist(t))()
+       bool t;
+{
+       register int (*P)();
+
+       listf = t;
+       P = Putchar;
+       Putchar = t ? listchar : normchar;
+       return (P);
+}
+
+int (*
+setnumb(t))()
+       bool t;
+{
+       register int (*P)();
+
+       numberf = t;
+       P = Pline;
+       Pline = t ? numbline : normline;
+       return (P);
+}
+
+/*
+ * Format c for list mode; leave things in common
+ * with normal print mode to be done by normchar.
+ */
+listchar(c)
+       register short c;
+{
+
+       c &= (TRIM|QUOTE);
+       switch (c) {
+
+       case '\t':
+       case '\b':
+               outchar('^');
+               c = ctlof(c);
+               break;
+
+       case '\n':
+               break;
+
+       case '\n' | QUOTE:
+               outchar('$');
+               break;
+
+       default:
+               if (c & QUOTE)
+                       break;
+               if (c < ' ' && c != '\n' || c == DELETE)
+                       outchar('^'), c = ctlof(c);
+               break;
+       }
+       normchar(c);
+}
+
+/*
+ * Format c for printing.  Handle funnies of upper case terminals
+ * and crocky hazeltines which don't have ~.
+ */
+normchar(c)
+       register short c;
+{
+       register char *colp;
+
+       c &= (TRIM|QUOTE);
+       if (c == '~' && HZ) {
+               normchar('\\');
+               c = '^';
+       }
+       if (c & QUOTE)
+               switch (c) {
+
+               case ' ' | QUOTE:
+               case '\b' | QUOTE:
+                       break;
+
+               case QUOTE:
+                       return;
+
+               default:
+                       c &= TRIM;
+               }
+       else if (c < ' ' && (c != '\b' || !OS) && c != '\n' && c != '\t' || c == DELETE)
+               putchar('^'), c = ctlof(c);
+       else if (UPPERCASE)
+               if (isupper(c)) {
+                       outchar('\\');
+                       c = tolower(c);
+               } else {
+                       colp = "({)}!|^~'`";
+                       while (*colp++)
+                               if (c == *colp++) {
+                                       outchar('\\');
+                                       c = colp[-2];
+                                       break;
+                               }
+               }
+       outchar(c);
+}
+
+/*
+ * Print a line with a number.
+ */
+numbline(i)
+       int i;
+{
+
+       if (shudclob)
+               slobber(' ');
+       printf("%6d  ", i);
+       normline();
+}
+
+/*
+ * Normal line output, no numbering.
+ */
+normline()
+{
+       register char *cp;
+
+       if (shudclob)
+               slobber(linebuf[0]);
+       /* pdp-11 doprnt is not reentrant so can't use "printf" here
+          in case we are tracing */
+       for (cp = linebuf; *cp;)
+               putchar(*cp++);
+       if (!inopen)
+               putchar('\n' | QUOTE);
+}
+
+/*
+ * Given c at the beginning of a line, determine whether
+ * the printing of the line will erase or otherwise obliterate
+ * the prompt which was printed before.  If it won't, do it now.
+ */
+slobber(c)
+       int c;
+{
+
+       shudclob = 0;
+       switch (c) {
+
+       case '\t':
+               if (Putchar == listchar)
+                       return;
+               break;
+
+       default:
+               return;
+
+       case ' ':
+       case 0:
+               break;
+       }
+       if (OS)
+               return;
+       flush();
+       putch(' ');
+       if (BC)
+               tputs(BC, 0, putch);
+       else
+               putch('\b');
+}
+
+/*
+ * The output buffer is initialized with a useful error
+ * message so we don't have to keep it in data space.
+ */
+static char linb[66] = {
+       'E', 'r', 'r', 'o', 'r', ' ', 'm', 'e', 's', 's', 'a', 'g', 'e', ' ',
+       'f', 'i', 'l', 'e', ' ', 'n', 'o', 't', ' ',
+       'a', 'v', 'a', 'i', 'l', 'a', 'b', 'l', 'e', '\n', 0
+};
+static char *linp = linb + 33;
+
+/*
+ * Phadnl records when we have already had a complete line ending with \n.
+ * If another line starts without a flush, and the terminal suggests it,
+ * we switch into -nl mode so that we can send lineffeeds to avoid
+ * a lot of spacing.
+ */
+static bool phadnl;
+
+/*
+ * Indirect to current definition of putchar.
+ */
+putchar(c)
+       int c;
+{
+
+       (*Putchar)(c);
+}
+
+/*
+ * Termchar routine for command mode.
+ * Watch for possible switching to -nl mode.
+ * Otherwise flush into next level of buffering when
+ * small buffer fills or at a newline.
+ */
+termchar(c)
+       int c;
+{
+
+       if (pfast == 0 && phadnl)
+               pstart();
+       if (c == '\n')
+               phadnl = 1;
+       else if (linp >= &linb[63])
+               flush1();
+       *linp++ = c;
+       if (linp >= &linb[63]) {
+               fgoto();
+               flush1();
+       }
+}
+
+flush()
+{
+
+       flush1();
+       flush2();
+}
+
+/*
+ * Flush from small line buffer into output buffer.
+ * Work here is destroying motion into positions, and then
+ * letting fgoto do the optimized motion.
+ */
+flush1()
+{
+       register char *lp;
+       register short c;
+
+       *linp = 0;
+       lp = linb;
+       while (*lp)
+               switch (c = *lp++) {
+
+               case '\r':
+                       destline += destcol / COLUMNS;
+                       destcol = 0;
+                       continue;
+
+               case '\b':
+                       if (destcol)
+                               destcol--;
+                       continue;
+
+               case ' ':
+                       destcol++;
+                       continue;
+
+               case '\t':
+                       destcol += value(TABSTOP) - destcol % value(TABSTOP);
+                       continue;
+
+               case '\n':
+                       destline += destcol / COLUMNS + 1;
+                       if (destcol != 0 && destcol % COLUMNS == 0)
+                               destline--;
+                       destcol = 0;
+                       continue;
+
+               default:
+                       fgoto();
+                       for (;;) {
+                               if (AM == 0 && outcol == COLUMNS)
+                                       fgoto();
+                               c &= TRIM;
+                               putch(c);
+                               if (c == '\b') {
+                                       outcol--;
+                                       destcol--;
+                               } else if (c >= ' ' && c != DELETE) {
+                                       outcol++;
+                                       destcol++;
+                                       if (XN && outcol % COLUMNS == 0)
+                                               putch('\n');
+                               }
+                               c = *lp++;
+                               if (c <= ' ')
+                                       break;
+                       }
+                       --lp;
+                       continue;
+               }
+       linp = linb;
+}
+
+flush2()
+{
+
+       fgoto();
+       flusho();
+       pstop();
+}
+
+/*
+ * Sync the position of the output cursor.
+ * Most work here is rounding for terminal boundaries getting the
+ * column position implied by wraparound or the lack thereof and
+ * rolling up the screen to get destline on the screen.
+ */
+fgoto()
+{
+       register int l, c;
+
+       if (destcol > COLUMNS - 1) {
+               destline += destcol / COLUMNS;
+               destcol %= COLUMNS;
+       }
+       if (outcol > COLUMNS - 1) {
+               l = (outcol + 1) / COLUMNS;
+               outline += l;
+               outcol %= COLUMNS;
+               if (AM == 0) {
+                       while (l > 0) {
+                               if (pfast)
+                                       putch('\r');
+                               putch('\n');
+                               l--;
+                       }
+                       outcol = 0;
+               }
+               if (outline > LINES - 1) {
+                       destline -= outline - (LINES - 1);
+                       outline = LINES - 1;
+               }
+       }
+       if (destline > LINES - 1) {
+               l = destline;
+               destline = LINES - 1;
+               if (outline < LINES - 1) {
+                       c = destcol;
+                       if (pfast == 0 && (!CA || holdcm))
+                               destcol = 0;
+                       fgoto();
+                       destcol = c;
+               }
+               while (l > LINES - 1) {
+                       putch('\n');
+                       l--;
+                       if (pfast == 0)
+                               outcol = 0;
+               }
+       }
+       if (destline < outline && !(CA && !holdcm || UP != NOSTR))
+               destline = outline;
+       if (CA && !holdcm)
+               if (plod(costCM) > 0)
+                       plod(0);
+               else
+                       tputs(tgoto(CM, destcol, destline), 0, putch);
+       else
+               plod(0);
+       outline = destline;
+       outcol = destcol;
+}
+
+/*
+ * Tab to column col by flushing and then setting destcol.
+ * Used by "set all".
+ */
+tab(col)
+       int col;
+{
+
+       flush1();
+       destcol = col;
+}
+
+/*
+ * Move (slowly) to destination.
+ * Hard thing here is using home cursor on really deficient terminals.
+ * Otherwise just use cursor motions, hacking use of tabs and overtabbing
+ * and backspace.
+ */
+
+static int plodcnt, plodflg;
+
+plodput(c)
+{
+
+       if (plodflg)
+               plodcnt--;
+       else
+               putch(c);
+}
+
+plod(cnt)
+{
+       register int i, j, k;
+       register int soutcol, soutline;
+
+       plodcnt = plodflg = cnt;
+       soutcol = outcol;
+       soutline = outline;
+       if (HO) {
+               if (GT)
+               i = (destcol / value(HARDTABS)) + (destcol % value(HARDTABS));
+               else
+                       i = destcol;
+       if (destcol >= outcol) {
+               j = destcol / value(HARDTABS) - outcol / value(HARDTABS);
+               if (GT && j)
+                       j += destcol % value(HARDTABS);
+                       else
+                               j = destcol - outcol;
+       } else
+                       if (outcol - destcol <= i && (BS || BC))
+                               i = j = outcol - destcol;
+                       else
+                               j = i + 1;
+               k = outline - destline;
+               if (k < 0)
+                       k = -k;
+               j += k;
+               if (i + destline < j) {
+                       tputs(HO, 0, plodput);
+                       outcol = outline = 0;
+               } else if (LL) {
+                       k = (LINES - 1) - destline;
+                       if (i + k + 2 < j) {
+                               tputs(LL, 0, plodput);
+                               outcol = 0;
+                               outline = LINES - 1;
+                       }
+               }
+       }
+       if (GT)
+       i = destcol % value(HARDTABS) + destcol / value(HARDTABS);
+       else
+               i = destcol;
+/*
+       if (BT && outcol > destcol && (j = (((outcol+7) & ~7) - destcol - 1) >> 3)) {
+               j *= (k = strlen(BT));
+               if ((k += (destcol&7)) > 4)
+                       j += 8 - (destcol&7);
+               else
+                       j += k;
+       } else
+*/
+               j = outcol - destcol;
+       /*
+        * If we will later need a \n which will turn into a \r\n by
+        * the system or the terminal, then don't bother to try to \r.
+        */
+       if ((NONL || !pfast) && outline < destline)
+               goto dontcr;
+       /*
+        * If the terminal will do a \r\n and there isn't room for it,
+        * then we can't afford a \r.
+        */
+       if (NC && outline >= destline)
+               goto dontcr;
+       /*
+        * If it will be cheaper, or if we can't back up, then send
+        * a return preliminarily.
+        */
+       if (j > i + 1 || outcol > destcol && !BS && !BC) {
+               plodput('\r');
+               if (NC) {
+                       plodput('\n');
+                       outline++;
+               }
+               outcol = 0;
+       }
+dontcr:
+       while (outline < destline) {
+               outline++;
+               plodput('\n');
+               if (plodcnt < 0)
+                       goto out;
+               if (NONL || pfast == 0)
+                       outcol = 0;
+       }
+       if (BT)
+               k = strlen(BT);
+       while (outcol > destcol) {
+               if (plodcnt < 0)
+                       goto out;
+/*
+               if (BT && !insmode && outcol - destcol > 4+k) {
+                       tputs(BT, 0, plodput);
+                       outcol--;
+                       outcol &= ~7;
+                       continue;
+               }
+*/
+               outcol--;
+               if (BC)
+                       tputs(BC, 0, plodput);
+               else
+                       plodput('\b');
+       }
+       while (outline > destline) {
+               outline--;
+               tputs(UP, 0, plodput);
+               if (plodcnt < 0)
+                       goto out;
+       }
+       if (GT && !insmode && destcol - outcol > 1) {
+       for (;;) {
+               i = (outcol / value(HARDTABS) + 1) * value(HARDTABS);
+               if (i > destcol)
+                       break;
+                       if (TA)
+                               tputs(TA, 0, plodput);
+                       else
+                               plodput('\t');
+                       outcol = i;
+               }
+               if (destcol - outcol > 4 && i < COLUMNS && (BC || BS)) {
+                       if (TA)
+                               tputs(TA, 0, plodput);
+                       else
+                               plodput('\t');
+                       outcol = i;
+                       while (outcol > destcol) {
+                               outcol--;
+                               if (BC)
+                                       tputs(BC, 0, plodput);
+                               else
+                                       plodput('\b');
+                       }
+               }
+       }
+       while (outcol < destcol) {
+               if (inopen && ND)
+                       tputs(ND, 0, plodput);
+               else
+                       plodput(' ');
+               outcol++;
+               if (plodcnt < 0)
+                       goto out;
+       }
+out:
+       if (plodflg) {
+               outcol = soutcol;
+               outline = soutline;
+       }
+       return(plodcnt);
+}
+
+/*
+ * An input line arrived.
+ * Calculate new (approximate) screen line position.
+ * Approximate because kill character echoes newline with
+ * no feedback and also because of long input lines.
+ */
+noteinp()
+{
+
+       outline++;
+       if (outline > LINES - 1)
+               outline = LINES - 1;
+       destline = outline;
+       destcol = outcol = 0;
+}
+
+/*
+ * Something weird just happened and we
+ * lost track of whats happening out there.
+ * Since we cant, in general, read where we are
+ * we just reset to some known state.
+ * On cursor addressible terminals setting to unknown
+ * will force a cursor address soon.
+ */
+termreset()
+{
+
+       endim();
+       if (TI) /* otherwise it flushes anyway, and 'set tty=dumb' vomits */
+               putpad(TI);      /*adb change -- emit terminal initial sequence */
+       destcol = 0;
+       destline = LINES - 1;
+       if (CA) {
+               outcol = UKCOL;
+               outline = UKCOL;
+       } else {
+               outcol = destcol;
+               outline = destline;
+       }
+}
+
+/*
+ * Low level buffering, with the ability to drain
+ * buffered output without printing it.
+ */
+char   *obp = obuf;
+
+draino()
+{
+
+       obp = obuf;
+}
+
+flusho()
+{
+
+       if (obp != obuf) {
+               write(1, obuf, obp - obuf);
+               obp = obuf;
+       }
+}
+
+putnl()
+{
+
+       putchar('\n');
+}
+
+putS(cp)
+       char *cp;
+{
+
+       if (cp == NULL)
+               return;
+       while (*cp)
+               putch(*cp++);
+}
+
+
+putch(c)
+       int c;
+{
+
+       *obp++ = c;
+       if (obp >= &obuf[sizeof obuf])
+               flusho();
+}
+
+/*
+ * Miscellaneous routines related to output.
+ */
+
+/*
+ * Cursor motion.
+ */
+char *
+cgoto()
+{
+
+       return (tgoto(CM, destcol, destline));
+}
+
+/*
+ * Put with padding
+ */
+putpad(cp)
+       char *cp;
+{
+
+       flush();
+       tputs(cp, 0, putch);
+}
+
+/*
+ * Set output through normal command mode routine.
+ */
+setoutt()
+{
+
+       Outchar = termchar;
+}
+
+/*
+ * Printf (temporarily) in list mode.
+ */
+/*VARARGS2*/
+lprintf(cp, dp)
+       char *cp, *dp;
+{
+       register int (*P)();
+
+       P = setlist(1);
+       printf(cp, dp);
+       Putchar = P;
+}
+
+/*
+ * Newline + flush.
+ */
+putNFL()
+{
+
+       putnl();
+       flush();
+}
+
+/*
+ * Try to start -nl mode.
+ */
+pstart()
+{
+
+       if (NONL)
+               return;
+       if (!value(OPTIMIZE))
+               return;
+       if (ruptible == 0 || pfast)
+               return;
+       fgoto();
+       flusho();
+       pfast = 1;
+       normtty++;
+       tty.sg_flags = normf & ~(ECHO|XTABS|CRMOD);
+       sTTY(1);
+}
+
+/*
+ * Stop -nl mode.
+ */
+pstop()
+{
+
+       if (inopen)
+               return;
+       phadnl = 0;
+       linp = linb;
+       draino();
+       normal(normf);
+       pfast &= ~1;
+}
+
+/*
+ * Prep tty for open mode.
+ */
+ostart()
+{
+       int f;
+
+       if (!intty)
+               error("Open and visual must be used interactively");
+       gTTY(1);
+       normtty++;
+       f = tty.sg_flags;
+#ifdef CBREAK
+       tty.sg_flags = (normf &~ (ECHO|XTABS|CRMOD)) | CBREAK;
+#else
+       tty.sg_flags = (normf &~ (ECHO|XTABS|CRMOD)) | RAW;
+#endif
+#ifdef TIOCGETC
+       nttyc.t_quitc = nttyc.t_startc = nttyc.t_stopc = '\377';
+#endif
+       sTTY(1);
+       putpad(VS);
+       putpad(KS);
+       pfast |= 2;
+       return (f);
+}
+
+/*
+ * Stop open, restoring tty modes.
+ */
+ostop(f)
+       int f;
+{
+
+       pfast = (f & CRMOD) == 0;
+       termreset(), fgoto(), flusho();
+       normal(f);
+       putpad(VE);
+       putpad(KE);
+}
+
+#ifndef CBREAK
+/*
+ * Into cooked mode for interruptibility.
+ */
+vcook()
+{
+
+       tty.sg_flags &= ~RAW;
+       sTTY(1);
+}
+
+/*
+ * Back into raw mode.
+ */
+vraw()
+{
+
+       tty.sg_flags |= RAW;
+       sTTY(1);
+}
+#endif
+
+/*
+ * Restore flags to normal state f.
+ */
+normal(f)
+       int f;
+{
+
+       if (normtty > 0) {
+               setty(f);
+               normtty--;
+       }
+}
+
+/*
+ * Straight set of flags to state f.
+ */
+setty(f)
+       int f;
+{
+       register int ot = tty.sg_flags;
+
+#ifdef TIOCGETC
+       if (f == normf)
+               nttyc = ottyc;
+       else
+               nttyc.t_quitc = nttyc.t_startc = nttyc.t_stopc = '\377';
+#endif
+       tty.sg_flags = f;
+       sTTY(1);
+       return (ot);
+}
+
+gTTY(i)
+       int i;
+{
+
+       ignore(gtty(i, &tty));
+#ifdef TIOCGETC
+       ioctl(i, TIOCGETC, &ottyc);
+       nttyc = ottyc;
+#endif
+}
+
+sTTY(i)
+       int i;
+{
+
+/*
+ * Bug in USG tty driver, put out a null char as a patch.
+ */
+#ifdef USG
+       if (tty.sg_ospeed == B1200)
+               write(1, "", 1);
+#endif
+#ifdef TIOCSETN
+       ioctl(i, TIOCSETN, &tty);
+#else
+       stty(i, &tty);
+#endif
+#ifdef TIOCSETC
+       ioctl(i, TIOCSETC, &nttyc);
+#endif
+}
+
+/*
+ * Print newline, or blank if in open/visual
+ */
+noonl()
+{
+
+       putchar(Outchar != termchar ? ' ' : '\n');
+}
diff --git a/usr/src/cmd/ex/ex_re.c b/usr/src/cmd/ex/ex_re.c
new file mode 100644 (file)
index 0000000..93abfaa
--- /dev/null
@@ -0,0 +1,880 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_re.h"
+
+/*
+ * Global, substitute and regular expressions.
+ * Very similar to ed, with some re extensions and
+ * confirmed substitute.
+ */
+global(k)
+       bool k;
+{
+       register char *gp;
+       register int c;
+       register line *a1;
+       char globuf[GBSIZE], *Cwas;
+       int lines = lineDOL();
+       int oinglobal = inglobal;
+       char *oglobp = globp;
+
+       Cwas = Command;
+       /*
+        * States of inglobal:
+        *  0: ordinary - not in a global command.
+        *  1: text coming from some buffer, not tty.
+        *  2: like 1, but the source of the buffer is a global command.
+        * Hence you're only in a global command if inglobal==2. This
+        * strange sounding convention is historically derived from
+        * everybody simulating a global command.
+        */
+       if (inglobal==2)
+               error("Global within global@not allowed");
+       markDOT();
+       setall();
+       nonzero();
+       if (skipend())
+               error("Global needs re|Missing regular expression for global");
+       c = getchar();
+       ignore(compile(c, 1));
+       savere(scanre);
+       gp = globuf;
+       while ((c = getchar()) != '\n') {
+               switch (c) {
+
+               case EOF:
+                       c = '\n';
+                       goto brkwh;
+
+               case '\\':
+                       c = getchar();
+                       switch (c) {
+
+                       case '\\':
+                               ungetchar(c);
+                               break;
+
+                       case '\n':
+                               break;
+
+                       default:
+                               *gp++ = '\\';
+                               break;
+                       }
+                       break;
+               }
+               *gp++ = c;
+               if (gp >= &globuf[GBSIZE - 2])
+                       error("Global command too long");
+       }
+brkwh:
+       ungetchar(c);
+out:
+       newline();
+       *gp++ = c;
+       *gp++ = 0;
+       inglobal = 2;
+       for (a1 = one; a1 <= dol; a1++) {
+               *a1 &= ~01;
+               if (a1 >= addr1 && a1 <= addr2 && execute(0, a1) == k)
+                       *a1 |= 01;
+       }
+       /* should use gdelete from ed to avoid n**2 here on g/.../d */
+       saveall();
+       if (inopen)
+               inopen = -1;
+       for (a1 = one; a1 <= dol; a1++) {
+               if (*a1 & 01) {
+                       *a1 &= ~01;
+                       dot = a1;
+                       globp = globuf;
+                       commands(1, 1);
+                       a1 = zero;
+               }
+       }
+       globp = oglobp;
+       inglobal = oinglobal;
+       endline = 1;
+       Command = Cwas;
+       netchHAD(lines);
+       setlastchar(EOF);
+       if (inopen) {
+               ungetchar(EOF);
+               inopen = 1;
+       }
+}
+
+bool   xflag;
+int    scount, slines, stotal;
+
+substitute(c)
+       int c;
+{
+       register line *addr;
+       register int n;
+       int gsubf;
+
+       gsubf = compsub(c);
+       if (!inglobal)
+               save12(), undkind = UNDCHANGE;
+       stotal = 0;
+       slines = 0;
+       for (addr = addr1; addr <= addr2; addr++) {
+               scount = 0;
+               if (dosubcon(0, addr) == 0)
+                       continue;
+               if (gsubf) {
+#ifdef notdef
+                       /*
+                        * should check but loc2 is already munged.
+                        * This needs a fancier check later.
+                        */
+                       if (loc1 == loc2)
+                               error("substitution loop");
+#endif
+                       while (*loc2)
+                               if (dosubcon(1, addr) == 0)
+                                       break;
+               }
+               if (scount) {
+                       stotal += scount;
+                       slines++;
+                       putmark(addr);
+                       n = append(getsub, addr);
+                       addr += n;
+                       addr2 += n;
+               }
+       }
+       if (stotal == 0 && !inglobal && !xflag)
+               error("Fail|Substitute pattern match failed");
+       snote(stotal, slines);
+       return (stotal);
+}
+
+compsub(ch)
+{
+       register int seof, c, uselastre;
+       static int gsubf;
+
+       if (!value(EDCOMPATIBLE))
+               gsubf = xflag = 0;
+       uselastre = 0;
+       switch (ch) {
+
+       case 's':
+               ignore(skipwh());
+               seof = getchar();
+               if (endcmd(seof) || any(seof, "gcr")) {
+                       ungetchar(seof);
+                       goto redo;
+               }
+               if (isalpha(seof) || isdigit(seof))
+                       error("Substitute needs re|Missing regular expression for substitute");
+               seof = compile(seof, 1);
+               uselastre = 1;
+               comprhs(seof);
+               gsubf = 0;
+               xflag = 0;
+               break;
+
+       case '~':
+               uselastre = 1;
+               /* fall into ... */
+       case '&':
+       redo:
+               if (re.Expbuf[0] == 0)
+                       error("No previous re|No previous regular expression");
+               break;
+       }
+       for (;;) {
+               c = getchar();
+               switch (c) {
+
+               case 'g':
+                       gsubf = !gsubf;
+                       continue;
+
+               case 'c':
+                       xflag = !xflag;
+                       continue;
+
+               case 'r':
+                       uselastre = 1;
+                       continue;
+
+               default:
+                       ungetchar(c);
+                       setcount();
+                       newline();
+                       if (uselastre)
+                               savere(subre);
+                       else
+                               resre(subre);
+                       return (gsubf);
+               }
+       }
+}
+
+comprhs(seof)
+       int seof;
+{
+       register char *rp, *orp;
+       register int c;
+       char orhsbuf[LBSIZE / 2];
+
+       rp = rhsbuf;
+       CP(orhsbuf, rp);
+       for (;;) {
+               c = getchar();
+               if (c == seof)
+                       break;
+               switch (c) {
+
+               case '\\':
+                       c = getchar();
+                       if (c == EOF) {
+                               ungetchar(c);
+                               break;
+                       }
+                       if (value(MAGIC)) {
+                               /*
+                                * When "magic", \& turns into a plain &,
+                                * and all other chars work fine quoted.
+                                */
+                               if (c != '&')
+                                       c |= QUOTE;
+                               break;
+                       }
+magic:
+                       if (c == '~') {
+                               for (orp = orhsbuf; *orp; *rp++ = *orp++)
+                                       if (rp >= &rhsbuf[LBSIZE / 2 + 1])
+                                               goto toobig;
+                               continue;
+                       }
+                       c |= QUOTE;
+                       break;
+
+               case '\n':
+               case EOF:
+                       ungetchar(c);
+                       goto endrhs;
+
+               case '~':
+               case '&':
+                       if (value(MAGIC))
+                               goto magic;
+                       break;
+               }
+               if (rp >= &rhsbuf[LBSIZE / 2 - 1])
+toobig:
+                       error("Replacement pattern too long@- limit 256 characters");
+               *rp++ = c;
+       }
+endrhs:
+       *rp++ = 0;
+}
+
+getsub()
+{
+       register char *p;
+
+       if ((p = linebp) == 0)
+               return (EOF);
+       strcLIN(p);
+       linebp = 0;
+       return (0);
+}
+
+dosubcon(f, a)
+       bool f;
+       line *a;
+{
+
+       if (execute(f, a) == 0)
+               return (0);
+       if (confirmed(a)) {
+               dosub();
+               scount++;
+       }
+       return (1);
+}
+
+confirmed(a)
+       line *a;
+{
+       register int c, ch;
+
+       if (xflag == 0)
+               return (1);
+       pofix();
+       pline(lineno(a));
+       if (inopen)
+               putchar('\n' | QUOTE);
+       c = column(loc1 - 1);
+       ugo(c - 1 + (inopen ? 1 : 0), ' ');
+       ugo(column(loc2 - 1) - c, '^');
+       flush();
+       ch = c = getkey();
+again:
+       if (c == '\r')
+               c = '\n';
+       if (inopen)
+               putchar(c), flush();
+       if (c != '\n' && c != EOF) {
+               c = getkey();
+               goto again;
+       }
+       noteinp();
+       return (ch == 'y');
+}
+
+getch()
+{
+       char c;
+
+       if (read(2, &c, 1) != 1)
+               return (EOF);
+       return (c & TRIM);
+}
+
+ugo(cnt, with)
+       int with;
+       int cnt;
+{
+
+       if (cnt > 0)
+               do
+                       putchar(with);
+               while (--cnt > 0);
+}
+
+int    casecnt;
+bool   destuc;
+
+dosub()
+{
+       register char *lp, *sp, *rp;
+       int c;
+
+       lp = linebuf;
+       sp = genbuf;
+       rp = rhsbuf;
+       while (lp < loc1)
+               *sp++ = *lp++;
+       casecnt = 0;
+       while (c = *rp++) {
+               if (c & QUOTE)
+                       switch (c & TRIM) {
+
+                       case '&':
+                               sp = place(sp, loc1, loc2);
+                               if (sp == 0)
+                                       goto ovflo;
+                               continue;
+
+                       case 'l':
+                               casecnt = 1;
+                               destuc = 0;
+                               continue;
+
+                       case 'L':
+                               casecnt = LBSIZE;
+                               destuc = 0;
+                               continue;
+
+                       case 'u':
+                               casecnt = 1;
+                               destuc = 1;
+                               continue;
+
+                       case 'U':
+                               casecnt = LBSIZE;
+                               destuc = 1;
+                               continue;
+
+                       case 'E':
+                       case 'e':
+                               casecnt = 0;
+                               continue;
+                       }
+               if (c < 0 && (c &= TRIM) >= '1' && c < nbra + '1') {
+                       sp = place(sp, braslist[c - '1'], braelist[c - '1']);
+                       if (sp == 0)
+                               goto ovflo;
+                       continue;
+               }
+               if (casecnt)
+                       *sp++ = fixcase(c & TRIM);
+               else
+                       *sp++ = c & TRIM;
+               if (sp >= &genbuf[LBSIZE])
+ovflo:
+                       error("Line overflow@in substitute");
+       }
+       lp = loc2;
+       loc2 = sp + (linebuf - genbuf);
+       while (*sp++ = *lp++)
+               if (sp >= &genbuf[LBSIZE])
+                       goto ovflo;
+       strcLIN(genbuf);
+}
+
+fixcase(c)
+       register int c;
+{
+
+       if (casecnt == 0)
+               return (c);
+       casecnt--;
+       if (destuc) {
+               if (islower(c))
+                       c = toupper(c);
+       } else
+               if (isupper(c))
+                       c = tolower(c);
+       return (c);
+}
+
+char *
+place(sp, l1, l2)
+       register char *sp, *l1, *l2;
+{
+
+       while (l1 < l2) {
+               *sp++ = fixcase(*l1++);
+               if (sp >= &genbuf[LBSIZE])
+                       return (0);
+       }
+       return (sp);
+}
+
+snote(total, lines)
+       register int total, lines;
+{
+
+       if (!notable(total))
+               return;
+       printf(mesg("%d subs|%d substitutions"), total);
+       if (lines != 1 && lines != total)
+               printf(" on %d lines", lines);
+       noonl();
+       flush();
+}
+
+compile(eof, oknl)
+       int eof;
+       int oknl;
+{
+       register int c;
+       register char *ep;
+       char *lastep;
+       char bracket[NBRA], *bracketp, *rhsp;
+       int cclcnt;
+
+       if (isalpha(eof) || isdigit(eof))
+               error("Regular expressions cannot be delimited by letters or digits");
+       ep = expbuf;
+       c = getchar();
+       if (eof == '\\')
+               switch (c) {
+
+               case '/':
+               case '?':
+                       if (scanre.Expbuf[0] == 0)
+error("No previous scan re|No previous scanning regular expression");
+                       resre(scanre);
+                       return (c);
+
+               case '&':
+                       if (subre.Expbuf[0] == 0)
+error("No previous substitute re|No previous substitute regular expression");
+                       resre(subre);
+                       return (c);
+
+               default:
+                       error("Badly formed re|Regular expression \\ must be followed by / or ?");
+               }
+       if (c == eof || c == '\n' || c == EOF) {
+               if (*ep == 0)
+                       error("No previous re|No previous regular expression");
+               if (c == '\n' && oknl == 0)
+                       error("Missing closing delimiter@for regular expression");
+               if (c != eof)
+                       ungetchar(c);
+               return (eof);
+       }
+       bracketp = bracket;
+       nbra = 0;
+       circfl = 0;
+       if (c == '^') {
+               c = getchar();
+               circfl++;
+       }
+       ungetchar(c);
+       for (;;) {
+               if (ep >= &expbuf[ESIZE - 2])
+complex:
+                       cerror("Re too complex|Regular expression too complicated");
+               c = getchar();
+               if (c == eof || c == EOF) {
+                       if (bracketp != bracket)
+cerror("Unmatched \\(|More \\('s than \\)'s in regular expression");
+                       *ep++ = CEOF;
+                       if (c == EOF)
+                               ungetchar(c);
+                       return (eof);
+               }
+               if (value(MAGIC)) {
+                       if (c != '*' || ep == expbuf)
+                               lastep = ep;
+               } else
+                       if (c != '\\' || peekchar() != '*' || ep == expbuf)
+                               lastep = ep;
+               switch (c) {
+
+               case '\\':
+                       c = getchar();
+                       switch (c) {
+
+                       case '(':
+                               if (nbra >= NBRA)
+cerror("Awash in \\('s!|Too many \\('d subexressions in a regular expression");
+                               *bracketp++ = nbra;
+                               *ep++ = CBRA;
+                               *ep++ = nbra++;
+                               continue;
+
+                       case ')':
+                               if (bracketp <= bracket)
+cerror("Extra \\)|More \\)'s than \\('s in regular expression");
+                               *ep++ = CKET;
+                               *ep++ = *--bracketp;
+                               continue;
+
+                       case '<':
+                               *ep++ = CBRC;
+                               continue;
+
+                       case '>':
+                               *ep++ = CLET;
+                               continue;
+                       }
+                       if (value(MAGIC) == 0)
+magic:
+                       switch (c) {
+
+                       case '.':
+                               *ep++ = CDOT;
+                               continue;
+
+                       case '~':
+                               rhsp = rhsbuf;
+                               while (*rhsp) {
+                                       if (*rhsp & QUOTE) {
+                                               c = *rhsp & TRIM;
+                                               if (c == '&')
+error("Replacement pattern contains &@- cannot use in re");
+                                               if (c >= '1' && c <= '9')
+error("Replacement pattern contains \\d@- cannot use in re");
+                                       }
+                                       if (ep >= &expbuf[ESIZE-2])
+                                               goto complex;
+                                       *ep++ = CCHR;
+                                       *ep++ = *rhsp++ & TRIM;
+                               }
+                               continue;
+
+                       case '*':
+                               if (ep == expbuf)
+                                       break;
+                               if (*lastep == CBRA || *lastep == CKET)
+cerror("Illegal *|Can't * a \\( ... \\) in regular expression");
+                               if (*lastep == CCHR && (lastep[1] & QUOTE))
+cerror("Illegal *|Can't * a \\n in regular expression");
+                               *lastep |= STAR;
+                               continue;
+
+                       case '[':
+                               *ep++ = CCL;
+                               *ep++ = 0;
+                               cclcnt = 1;
+                               c = getchar();
+                               if (c == '^') {
+                                       c = getchar();
+                                       ep[-2] = NCCL;
+                               }
+                               if (c == ']')
+cerror("Bad character class|Empty character class '[]' or '[^]' cannot match");
+                               while (c != ']') {
+                                       if (c == '\\' && any(peekchar(), "]-^\\"))
+                                               c = getchar() | QUOTE;
+                                       if (c == '\n' || c == EOF)
+                                               cerror("Missing ]");
+                                       *ep++ = c;
+                                       cclcnt++;
+                                       if (ep >= &expbuf[ESIZE])
+                                               goto complex;
+                                       c = getchar();
+                               }
+                               lastep[1] = cclcnt;
+                               continue;
+                       }
+                       if (c == EOF) {
+                               ungetchar(EOF);
+                               c = '\\';
+                               goto defchar;
+                       }
+                       *ep++ = CCHR;
+                       if (c == '\n')
+cerror("No newlines in re's|Can't escape newlines into regular expressions");
+/*
+                       if (c < '1' || c > NBRA + '1') {
+*/
+                               *ep++ = c;
+                               continue;
+/*
+                       }
+                       c -= '1';
+                       if (c >= nbra)
+cerror("Bad \\n|\\n in regular expression with n greater than the number of \\('s");
+                       *ep++ = c | QUOTE;
+                       continue;
+*/
+
+               case '\n':
+                       if (oknl) {
+                               ungetchar(c);
+                               *ep++ = CEOF;
+                               return (eof);
+                       }
+cerror("Badly formed re|Missing closing delimiter for regular expression");
+
+               case '$':
+                       if (peekchar() == eof || peekchar() == EOF || oknl && peekchar() == '\n') {
+                               *ep++ = CDOL;
+                               continue;
+                       }
+                       goto defchar;
+
+               case '.':
+               case '~':
+               case '*':
+               case '[':
+                       if (value(MAGIC))
+                               goto magic;
+defchar:
+               default:
+                       *ep++ = CCHR;
+                       *ep++ = c;
+                       continue;
+               }
+       }
+}
+
+cerror(s)
+       char *s;
+{
+
+       expbuf[0] = 0;
+       error(s);
+}
+
+same(a, b)
+       register int a, b;
+{
+
+       return (a == b || value(IGNORECASE) &&
+          ((islower(a) && toupper(a) == b) || (islower(b) && toupper(b) == a)));
+}
+
+char   *locs;
+
+execute(gf, addr)
+       line *addr;
+{
+       register char *p1, *p2;
+       register int c;
+
+       if (gf) {
+               if (circfl)
+                       return (0);
+#ifdef notdef
+               if (loc1 == loc2)
+                       loc2++;
+#endif
+               locs = p1 = loc2;
+       } else {
+               if (addr == zero)
+                       return (0);
+               p1 = linebuf;
+               getline(*addr);
+               locs = 0;
+       }
+       p2 = expbuf;
+       if (circfl) {
+               loc1 = p1;
+               return (advance(p1, p2));
+       }
+       /* fast check for first character */
+       if (*p2 == CCHR) {
+               c = p2[1];
+               do {
+                       if (c != *p1 && (!value(IGNORECASE) ||
+                          !((islower(c) && toupper(c) == *p1) ||
+                          (islower(*p1) && toupper(*p1) == c))))
+                               continue;
+                       if (advance(p1, p2)) {
+                               loc1 = p1;
+                               return (1);
+                       }
+               } while (*p1++);
+               return (0);
+       }
+       /* regular algorithm */
+       do {
+               if (advance(p1, p2)) {
+                       loc1 = p1;
+                       return (1);
+               }
+       } while (*p1++);
+       return (0);
+}
+
+#define        uletter(c)      (isalpha(c) || c == '_')
+
+advance(lp, ep)
+       register char *lp, *ep;
+{
+       register char *curlp;
+       char *sp, *sp1;
+       int c;
+
+       for (;;) switch (*ep++) {
+
+       case CCHR:
+/* useless
+               if (*ep & QUOTE) {
+                       c = *ep++ & TRIM;
+                       sp = braslist[c];
+                       sp1 = braelist[c];
+                       while (sp < sp1) {
+                               if (!same(*sp, *lp))
+                                       return (0);
+                               sp++, lp++;
+                       }
+                       continue;
+               }
+*/
+               if (!same(*ep, *lp))
+                       return (0);
+               ep++, lp++;
+               continue;
+
+       case CDOT:
+               if (*lp++)
+                       continue;
+               return (0);
+
+       case CDOL:
+               if (*lp == 0)
+                       continue;
+               return (0);
+
+       case CEOF:
+               loc2 = lp;
+               return (1);
+
+       case CCL:
+               if (cclass(ep, *lp++, 1)) {
+                       ep += *ep;
+                       continue;
+               }
+               return (0);
+
+       case NCCL:
+               if (cclass(ep, *lp++, 0)) {
+                       ep += *ep;
+                       continue;
+               }
+               return (0);
+
+       case CBRA:
+               braslist[*ep++] = lp;
+               continue;
+
+       case CKET:
+               braelist[*ep++] = lp;
+               continue;
+
+       case CDOT|STAR:
+               curlp = lp;
+               while (*lp++)
+                       continue;
+               goto star;
+
+       case CCHR|STAR:
+               curlp = lp;
+               while (same(*lp, *ep))
+                       lp++;
+               lp++;
+               ep++;
+               goto star;
+
+       case CCL|STAR:
+       case NCCL|STAR:
+               curlp = lp;
+               while (cclass(ep, *lp++, ep[-1] == (CCL|STAR)))
+                       continue;
+               ep += *ep;
+               goto star;
+star:
+               do {
+                       lp--;
+                       if (lp == locs)
+                               break;
+                       if (advance(lp, ep))
+                               return (1);
+               } while (lp > curlp);
+               return (0);
+
+       case CBRC:
+               if (lp == expbuf)
+                       continue;
+               if ((isdigit(*lp) || uletter(*lp)) && !uletter(lp[-1]) && !isdigit(lp[-1]))
+                       continue;
+               return (0);
+
+       case CLET:
+               if (!uletter(*lp) && !isdigit(*lp))
+                       continue;
+               return (0);
+
+       default:
+               error("Re internal error");
+       }
+}
+
+cclass(set, c, af)
+       register char *set;
+       register int c;
+       int af;
+{
+       register int n;
+
+       if (c == 0)
+               return (0);
+       if (value(IGNORECASE) && isupper(c))
+               c = tolower(c);
+       n = *set++;
+       while (--n)
+               if (n > 2 && set[1] == '-') {
+                       if (c >= (set[0] & TRIM) && c <= (set[2] & TRIM))
+                               return (af);
+                       set += 3;
+                       n -= 2;
+               } else
+                       if ((*set++ & TRIM) == c)
+                               return (af);
+       return (!af);
+}
diff --git a/usr/src/cmd/ex/ex_re.h b/usr/src/cmd/ex/ex_re.h
new file mode 100644 (file)
index 0000000..500d5d5
--- /dev/null
@@ -0,0 +1,66 @@
+/* Copyright (c) 1979 Regents of the University of California */
+/*
+ * Regular expression definitions.
+ * The regular expressions in ex are similar to those in ed,
+ * with the addition of the word boundaries from Toronto ed
+ * and allowing character classes to have [a-b] as in the shell.
+ * The numbers for the nodes below are spaced further apart then
+ * necessary because I at one time partially put in + and | (one or
+ * more and alternation.)
+ */
+struct regexp {
+       char    Expbuf[ESIZE + 2];
+       bool    Circfl;
+       short   Nbra;
+};
+
+/*
+ * There are three regular expressions here, the previous (in re),
+ * the previous substitute (in subre) and the previous scanning (in scanre).
+ * It would be possible to get rid of "re" by making it a stack parameter
+ * to the appropriate routines.
+ */
+struct regexp re;              /* Last re */
+struct regexp scanre;          /* Last scanning re */
+struct regexp subre;           /* Last substitute re */
+
+/*
+ * Defining circfl and expbuf like this saves us from having to change
+ * old code in the ex_re.c stuff.
+ */
+#define        expbuf  re.Expbuf
+#define        circfl  re.Circfl
+#define        nbra    re.Nbra
+
+/*
+ * Since the phototypesetter v7-epsilon
+ * C compiler doesn't have structure assignment...
+ */
+#define        savere(a)       copy(&a, &re, sizeof (struct regexp))
+#define        resre(a)        copy(&re, &a, sizeof (struct regexp))
+
+/*
+ * Definitions for substitute
+ */
+char   *braslist[NBRA];        /* Starts of \(\)'ed text in lhs */
+char   *braelist[NBRA];        /* Ends... */
+char   rhsbuf[RHSSIZE];        /* Rhs of last substitute */
+
+/*
+ * Definitions of codes for the compiled re's.
+ * The re algorithm is described in a paper
+ * by K. Thompson in the CACM about 10 years ago
+ * and is the same as in ed.
+ */
+#define        STAR    1
+
+#define        CBRA    1
+#define        CDOT    4
+#define        CCL     8
+#define        NCCL    12
+#define        CDOL    16
+#define        CEOF    17
+#define        CKET    18
+#define        CCHR    20
+#define        CBRC    24
+#define        CLET    25
diff --git a/usr/src/cmd/ex/ex_set.c b/usr/src/cmd/ex/ex_set.c
new file mode 100644 (file)
index 0000000..721836c
--- /dev/null
@@ -0,0 +1,197 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_temp.h"
+
+/*
+ * Set command.
+ */
+char   optname[ONMSZ];
+
+set()
+{
+       register char *cp;
+       register struct option *op;
+       register int c;
+       bool no;
+
+       setnoaddr();
+       if (skipend()) {
+               if (peekchar() != EOF)
+                       ignchar();
+               propts();
+               return;
+       }
+       do {
+               cp = optname;
+               do {
+                       if (cp < &optname[ONMSZ - 2])
+                               *cp++ = getchar();
+               } while (isalpha(peekchar()));
+               *cp = 0;
+               cp = optname;
+               if (eq("all", cp)) {
+                       if (inopen)
+                               pofix();
+                       prall();
+                       goto next;
+               }
+               no = 0;
+               if (cp[0] == 'n' && cp[1] == 'o') {
+                       cp += 2;
+                       no++;
+               }
+               for (op = options; op < &options[NOPTS]; op++)
+                       if (eq(op->oname, cp) || op->oabbrev && eq(op->oabbrev, cp))
+                               break;
+               if (op->oname == 0)
+                       serror("%s: No such option@- 'set all' gives all option values", cp);
+               c = skipwh();
+               if (peekchar() == '?') {
+                       ignchar();
+printone:
+                       propt(op);
+                       noonl();
+                       goto next;
+               }
+               if (op->otype == ONOFF) {
+                       op->ovalue = 1 - no;
+                       goto next;
+               }
+               if (no)
+                       serror("Option %s is not a toggle", op->oname);
+               if (c != 0 || setend())
+                       goto printone;
+               if (getchar() != '=')
+                       serror("Missing =@in assignment to option %s", op->oname);
+               switch (op->otype) {
+
+               case NUMERIC:
+                       if (!isdigit(peekchar()))
+error("Digits required@after = when assigning numeric option");
+                       op->ovalue = getnum();
+                       if (value(TABSTOP) <= 0)
+                               value(TABSTOP) = TABS;
+                       break;
+
+               case STRING:
+               case OTERM:
+                       cp = optname;
+                       while (!setend()) {
+                               if (cp >= &optname[ONMSZ])
+                                       error("String too long@in option assignment");
+                               /* adb change:  allow whitepace in strings */
+                               if( (*cp = getchar()) == '\\')
+                                       if( peekchar() != EOF)
+                                               *cp = getchar();
+                               cp++;
+                       }
+                       *cp = 0;
+                       if (op->otype == OTERM) {
+/*
+ * At first glance it seems like we shouldn't care if the terminal type
+ * is changed inside visual mode, as long as we assume the screen is
+ * a mess and redraw it. However, it's a much harder problem than that.
+ * If you happen to change from 1 crt to another that both have the same
+ * size screen, it's OK. But if the screen size if different, the stuff
+ * that gets initialized in vop() will be wrong. This could be overcome
+ * by redoing the initialization, e.g. making the first 90% of vop into
+ * a subroutine. However, the most useful case is where you forgot to do
+ * a setenv before you went into the editor and it thinks you're on a dumb
+ * terminal. Ex treats this like hardcopy and goes into HARDOPEN mode.
+ * This loses because the first part of vop calls oop in this case.
+ * The problem is so hard I gave up. I'm not saying it can't be done,
+ * but I am saying it probably isn't worth the effort.
+ */
+                               if (inopen)
+error("Can't change type of terminal from within open/visual");
+                               setterm(optname);
+                       } else {
+                               CP(op->osvalue, optname);
+                               op->odefault = 1;
+                       }
+                       break;
+               }
+next:
+               flush();
+       } while (!skipend());
+       eol();
+}
+
+setend()
+{
+
+       return (iswhite(peekchar()) || endcmd(peekchar()));
+}
+
+prall()
+{
+       register int incr = (NOPTS + 2) / 3;
+       register int rows = incr;
+       register struct option *op = options;
+
+       for (; rows; rows--, op++) {
+               propt(op);
+               tab(24);
+               propt(&op[incr]);
+               if (&op[2*incr] < &options[NOPTS]) {
+                       tab(56);
+                       propt(&op[2 * incr]);
+               }
+               putNFL();
+       }
+}
+
+propts()
+{
+       register struct option *op;
+
+       for (op = options; op < &options[NOPTS]; op++) {
+#ifdef V6
+               if (op == &options[TERM])
+#else
+               if (op == &options[TTYTYPE])
+#endif
+                       continue;
+               switch (op->otype) {
+
+               case ONOFF:
+               case NUMERIC:
+                       if (op->ovalue == op->odefault)
+                               continue;
+                       break;
+
+               case STRING:
+                       if (op->odefault == 0)
+                               continue;
+                       break;
+               }
+               propt(op);
+               putchar(' ');
+       }
+       noonl();
+       flush();
+}
+
+propt(op)
+       register struct option *op;
+{
+       register char *name;
+       
+       name = op->oname;
+
+       switch (op->otype) {
+
+       case ONOFF:
+               printf("%s%s", op->ovalue ? "" : "no", name);
+               break;
+
+       case NUMERIC:
+               printf("%s=%d", name, op->ovalue);
+               break;
+
+       case STRING:
+       case OTERM:
+               printf("%s=%s", name, op->osvalue);
+               break;
+       }
+}
diff --git a/usr/src/cmd/ex/ex_subr.c b/usr/src/cmd/ex/ex_subr.c
new file mode 100644 (file)
index 0000000..1e17f4c
--- /dev/null
@@ -0,0 +1,760 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_re.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * Random routines, in alphabetical order.
+ */
+
+any(c, s)
+       int c;
+       register char *s;
+{
+       register int x;
+
+       while (x = *s++)
+               if (x == c)
+                       return (1);
+       return (0);
+}
+
+backtab(i)
+       register int i;
+{
+       register int j;
+
+       j = i % value(SHIFTWIDTH);
+       if (j == 0)
+               j = value(SHIFTWIDTH);
+       i -= j;
+       if (i < 0)
+               i = 0;
+       return (i);
+}
+
+change()
+{
+
+       tchng++;
+       chng = tchng;
+}
+
+/*
+ * Column returns the number of
+ * columns occupied by printing the
+ * characters through position cp of the
+ * current line.
+ */
+column(cp)
+       register char *cp;
+{
+
+       if (cp == 0)
+               cp = &linebuf[LBSIZE - 2];
+       return (qcolumn(cp, (char *) 0));
+}
+
+Copy(to, from, size)
+       register char *from, *to;
+       register int size;
+{
+
+       if (size > 0)
+               do
+                       *to++ = *from++;
+               while (--size > 0);
+}
+
+copyw(to, from, size)
+       register line *from, *to;
+       register int size;
+{
+
+       if (size > 0)
+               do
+                       *to++ = *from++;
+               while (--size > 0);
+}
+
+copywR(to, from, size)
+       register line *from, *to;
+       register int size;
+{
+
+       while (--size >= 0)
+               to[size] = from[size];
+}
+
+ctlof(c)
+       int c;
+{
+
+       return (c == TRIM ? '?' : c | ('A' - 1));
+}
+
+dingdong()
+{
+
+       if (VB)
+               putpad(VB);
+       else if (value(ERRORBELLS))
+               putch('\207');
+}
+
+fixindent(indent)
+       int indent;
+{
+       register int i;
+       register char *cp;
+
+       i = whitecnt(genbuf);
+       cp = vpastwh(genbuf);
+       if (*cp == 0 && i == indent && linebuf[0] == 0) {
+               genbuf[0] = 0;
+               return (i);
+       }
+       CP(genindent(i), cp);
+       return (i);
+}
+
+filioerr(cp)
+       char *cp;
+{
+       register int oerrno = errno;
+
+       lprintf("\"%s\"", cp);
+       errno = oerrno;
+       syserror();
+}
+
+char *
+genindent(indent)
+       register int indent;
+{
+       register char *cp;
+
+       for (cp = genbuf; indent >= value(TABSTOP); indent -= value(TABSTOP))
+               *cp++ = '\t';
+       for (; indent > 0; indent--)
+               *cp++ = ' ';
+       return (cp);
+}
+
+getDOT()
+{
+
+       getline(*dot);
+}
+
+line *
+getmark(c)
+       register int c;
+{
+       register line *addr;
+       
+       for (addr = one; addr <= dol; addr++)
+               if (names[c - 'a'] == (*addr &~ 01)) {
+                       return (addr);
+               }
+       return (0);
+}
+
+getn(cp)
+       register char *cp;
+{
+       register int i = 0;
+
+       while (isdigit(*cp))
+               i = i * 10 + *cp++ - '0';
+       if (*cp)
+               return (0);
+       return (i);
+}
+
+ignnEOF()
+{
+       register int c = getchar();
+
+       if (c == EOF)
+               ungetchar(c);
+}
+
+iswhite(c)
+       int c;
+{
+
+       return (c == ' ' || c == '\t');
+}
+
+junk(c)
+       register int c;
+{
+
+       if (c && !value(BEAUTIFY))
+               return (0);
+       if (c >= ' ' && c != TRIM)
+               return (0);
+       switch (c) {
+
+       case '\t':
+       case '\n':
+       case '\f':
+               return (0);
+
+       default:
+               return (1);
+       }
+}
+
+killed()
+{
+
+       killcnt(addr2 - addr1 + 1);
+}
+
+killcnt(cnt)
+       register int cnt;
+{
+
+       if (inopen) {
+               notecnt = cnt;
+               notenam = notesgn = "";
+               return;
+       }
+       if (!notable(cnt))
+               return;
+       printf("%d lines", cnt);
+       if (value(TERSE) == 0) {
+               printf(" %c%s", Command[0] | ' ', Command + 1);
+               if (Command[strlen(Command) - 1] != 'e')
+                       putchar('e');
+               putchar('d');
+       }
+       putNFL();
+}
+
+lineno(a)
+       line *a;
+{
+
+       return (a - zero);
+}
+
+lineDOL()
+{
+
+       return (lineno(dol));
+}
+
+lineDOT()
+{
+
+       return (lineno(dot));
+}
+
+markDOT()
+{
+
+       markpr(dot);
+}
+
+markpr(which)
+       line *which;
+{
+
+       if ((inglobal == 0 || inopen) && which <= endcore) {
+               names['z'-'a'+1] = *which & ~01;
+               if (inopen)
+                       ncols['z'-'a'+1] = cursor;
+       }
+}
+
+markreg(c)
+       register int c;
+{
+
+       if (c == '\'' || c == '`')
+               return ('z' + 1);
+       if (c >= 'a' && c <= 'z')
+               return (c);
+       return (0);
+}
+
+/*
+ * Mesg decodes the terse/verbose strings. Thus
+ *     'xxx@yyy' -> 'xxx' if terse, else 'xxx yyy'
+ *     'xxx|yyy' -> 'xxx' if terse, else 'yyy'
+ * All others map to themselves.
+ */
+char *
+mesg(str)
+       register char *str;
+{
+       register char *cp;
+
+       str = strcpy(genbuf, str);
+       for (cp = str; *cp; cp++)
+               switch (*cp) {
+
+               case '@':
+                       if (value(TERSE))
+                               *cp = 0;
+                       else
+                               *cp = ' ';
+                       break;
+
+               case '|':
+                       if (value(TERSE) == 0)
+                               return (cp + 1);
+                       *cp = 0;
+                       break;
+               }
+       return (str);
+}
+
+/*VARARGS2*/
+merror(seekpt, i)
+#ifdef VMUNIX
+       char *seekpt;
+#else
+# ifdef lint
+       char *seekpt;
+# else
+       int seekpt;
+# endif
+#endif
+       int i;
+{
+       register char *cp = linebuf;
+
+       if (seekpt == 0)
+               return;
+       merror1(seekpt);
+       if (*cp == '\n')
+               putnl(), cp++;
+       if (inopen && CE)
+               vclreol();
+       if (SO && SE)
+               putpad(SO);
+       printf(mesg(cp), i);
+       if (SO && SE)
+               putpad(SE);
+}
+
+merror1(seekpt)
+#ifdef VMUNIX
+       char *seekpt;
+#else
+# ifdef lint
+       char *seekpt;
+# else
+       int seekpt;
+# endif
+#endif
+{
+
+#ifdef VMUNIX
+       strcpy(linebuf, seekpt);
+#else
+       lseek(erfile, (long) seekpt, 0);
+       if (read(erfile, linebuf, 128) < 2)
+               CP(linebuf, "ERROR");
+#endif
+}
+
+morelines()
+{
+
+       if ((int) sbrk(1024 * sizeof (line)) == -1)
+               return (-1);
+       endcore += 1024;
+       return (0);
+}
+
+nonzero()
+{
+
+       if (addr1 == zero) {
+               notempty();
+               error("Nonzero address required@on this command");
+       }
+}
+
+notable(i)
+       int i;
+{
+
+       return (hush == 0 && !inglobal && i > value(REPORT));
+}
+
+
+notempty()
+{
+
+       if (dol == zero)
+               error("No lines@in the buffer");
+}
+
+
+netchHAD(cnt)
+       int cnt;
+{
+
+       netchange(lineDOL() - cnt);
+}
+
+netchange(i)
+       register int i;
+{
+       register char *cp;
+
+       if (i > 0)
+               notesgn = cp = "more ";
+       else
+               notesgn = cp = "fewer ", i = -i;
+       if (inopen) {
+               notecnt = i;
+               notenam = "";
+               return;
+       }
+       if (!notable(i))
+               return;
+       printf(mesg("%d %slines@in file after %s"), i, cp, Command);
+       putNFL();
+}
+
+putmark(addr)
+       line *addr;
+{
+
+       putmk1(addr, putline());
+}
+
+putmk1(addr, n)
+       register line *addr;
+       int n;
+{
+       register line *markp;
+
+       *addr &= ~1;
+       for (markp = (anymarks ? names : &names['z'-'a'+1]);
+         markp <= &names['z'-'a'+1]; markp++)
+               if (*markp == *addr)
+                       *markp = n;
+       *addr = n;
+}
+
+char *
+plural(i)
+       long i;
+{
+
+       return (i == 1 ? "" : "s");
+}
+
+int    qcount();
+short  vcntcol;
+
+qcolumn(lim, gp)
+       register char *lim, *gp;
+{
+       register int x;
+       int (*OO)();
+
+       OO = Outchar;
+       Outchar = qcount;
+       vcntcol = 0;
+       if (lim != NULL)
+               x = lim[1], lim[1] = 0;
+       pline(0);
+       if (lim != NULL)
+               lim[1] = x;
+       if (gp)
+               while (*gp)
+                       putchar(*gp++);
+       Outchar = OO;
+       return (vcntcol);
+}
+
+int
+qcount(c)
+       int c;
+{
+
+       if (c == '\t') {
+               vcntcol += value(TABSTOP) - vcntcol % value(TABSTOP);
+               return;
+       }
+       vcntcol++;
+}
+
+reverse(a1, a2)
+       register line *a1, *a2;
+{
+       register line t;
+
+       for (;;) {
+               t = *--a2;
+               if (a2 <= a1)
+                       return;
+               *a2 = *a1;
+               *a1++ = t;
+       }
+}
+
+save(a1, a2)
+       line *a1;
+       register line *a2;
+{
+       register int more;
+
+       undkind = UNDNONE;
+       undadot = dot;
+       more = (a2 - a1 + 1) - (unddol - dol);
+       while (more > (endcore - truedol))
+               if (morelines() < 0)
+                       error("Out of memory@saving lines for undo - try using ed or re");
+       if (more)
+               (*(more > 0 ? copywR : copyw))(unddol + more + 1, unddol + 1,
+                   (truedol - unddol));
+       unddol += more;
+       truedol += more;
+       copyw(dol + 1, a1, a2 - a1 + 1);
+       undkind = UNDALL;
+       unddel = a1 - 1;
+       undap1 = a1;
+       undap2 = a2 + 1;
+}
+
+save12()
+{
+
+       save(addr1, addr2);
+}
+
+saveall()
+{
+
+       save(one, dol);
+}
+
+span()
+{
+
+       return (addr2 - addr1 + 1);
+}
+
+sync()
+{
+
+       chng = 0;
+       tchng = 0;
+       xchng = 0;
+}
+
+
+skipwh()
+{
+       register int wh;
+
+       wh = 0;
+       while (iswhite(peekchar())) {
+               wh++;
+               ignchar();
+       }
+       return (wh);
+}
+
+/*VARARGS2*/
+smerror(seekpt, cp)
+#ifdef lint
+       char *seekpt;
+#else
+       int seekpt;
+#endif
+       char *cp;
+{
+
+       if (seekpt == 0)
+               return;
+       merror1(seekpt);
+       if (inopen && CE)
+               vclreol();
+       if (SO && SE)
+               putpad(SO);
+       lprintf(mesg(linebuf), cp);
+       if (SO && SE)
+               putpad(SE);
+}
+
+#define        std_nerrs (sizeof std_errlist / sizeof std_errlist[0])
+
+#define        error(i)        i
+
+#ifdef lint
+char   *std_errlist[] = {
+#else
+#ifdef VMUNIX
+char   *std_errlist[] = {
+#else
+short  std_errlist[] = {
+#endif
+#endif
+       error("Error 0"),
+       error("Not super-user"),
+       error("No such file or directory"),
+       error("No such process"),
+       error("Interrupted system call"),
+       error("Physical I/O error"),
+       error("No such device or address"),
+       error("Argument list too long"),
+       error("Exec format error"),
+       error("Bad file number"),
+       error("No children"),
+       error("No more processes"),
+       error("Not enough core"),
+       error("Permission denied"),
+       error("Bad address"),
+       error("Block device required"),
+       error("Mount device busy"),
+       error("File exists"),
+       error("Cross-device link"),
+       error("No such device"),
+       error("Not a directory"),
+       error("Is a directory"),
+       error("Invalid argument"),
+       error("File table overflow"),
+       error("Too many open files"),
+       error("Not a typewriter"),
+       error("Text file busy"),
+       error("File too large"),
+       error("No space left on device"),
+       error("Illegal seek"),
+       error("Read-only file system"),
+       error("Too many links"),
+       error("Broken pipe")
+#ifndef QUOTA
+       , error("Math argument")
+       , error("Result too large")
+#else
+       , error("Quota exceeded")
+#endif
+};
+
+#undef error
+
+char *
+strend(cp)
+       register char *cp;
+{
+
+       while (*cp)
+               cp++;
+       return (cp);
+}
+
+strcLIN(dp)
+       char *dp;
+{
+
+       CP(linebuf, dp);
+}
+
+syserror()
+{
+       register int e = errno;
+
+       dirtcnt = 0;
+       putchar(' ');
+       if (e >= 0 && errno <= std_nerrs)
+               error(std_errlist[e]);
+       else
+               error("System error %d", e);
+}
+
+char *
+vfindcol(i)
+       int i;
+{
+       register char *cp;
+       register int (*OO)() = Outchar;
+
+       Outchar = qcount;
+       ignore(qcolumn(linebuf - 1, NOSTR));
+       for (cp = linebuf; *cp && vcntcol < i; cp++)
+               putchar(*cp);
+       if (cp != linebuf)
+               cp--;
+       Outchar = OO;
+       return (cp);
+}
+
+char *
+vskipwh(cp)
+       register char *cp;
+{
+
+       while (iswhite(*cp) && cp[1])
+               cp++;
+       return (cp);
+}
+
+
+char *
+vpastwh(cp)
+       register char *cp;
+{
+
+       while (iswhite(*cp))
+               cp++;
+       return (cp);
+}
+
+whitecnt(cp)
+       register char *cp;
+{
+       register int i;
+
+       i = 0;
+       for (;;)
+               switch (*cp++) {
+
+               case '\t':
+                       i += value(TABSTOP) - i % value(TABSTOP);
+                       break;
+
+               case ' ':
+                       i++;
+                       break;
+
+               default:
+                       return (i);
+               }
+}
+
+#ifdef lint
+Ignore(a)
+       char *a;
+{
+
+       a = a;
+}
+
+Ignorf(a)
+       int (*a)();
+{
+
+       a = a;
+}
+#endif
+
+markit(addr)
+       line *addr;
+{
+
+       if (addr != dot && addr >= one && addr <= dol)
+               markDOT();
+}
+
diff --git a/usr/src/cmd/ex/ex_temp.c b/usr/src/cmd/ex/ex_temp.c
new file mode 100644 (file)
index 0000000..e8f8780
--- /dev/null
@@ -0,0 +1,568 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_temp.h"
+#include "ex_vis.h"
+#include "ex_tty.h"
+
+/*
+ * Editor temporary file routines.
+ * Very similar to those of ed, except uses 2 input buffers.
+ */
+#define        READ    0
+#define        WRITE   1
+
+char   tfname[40];
+char   rfname[40];
+int    havetmp;
+short  tfile = -1;
+short  rfile = -1;
+
+fileinit()
+{
+       register char *p;
+       register int i, j;
+       struct stat stbuf;
+
+       if (tline == INCRMT * (HBLKS+2))
+               return;
+       cleanup(0);
+       close(tfile);
+       tline = INCRMT * (HBLKS+2);
+       blocks[0] = HBLKS;
+       blocks[1] = HBLKS+1;
+       blocks[2] = -1;
+       dirtcnt = 0;
+       iblock = -1;
+       iblock2 = -1;
+       oblock = -1;
+       CP(tfname, svalue(DIRECTORY));
+       if (stat(tfname, &stbuf)) {
+dumbness:
+               if (setexit() == 0)
+                       filioerr(tfname);
+               else
+                       putNFL();
+               cleanup(1);
+               exit(1);
+       }
+       if ((stbuf.st_mode & S_IFMT) != S_IFDIR) {
+               errno = ENOTDIR;
+               goto dumbness;
+       }
+       ichanged = 0;
+       ichang2 = 0;
+       ignore(strcat(tfname, "/ExXXXXX"));
+       for (p = strend(tfname), i = 5, j = getpid(); i > 0; i--, j /= 10)
+               *--p = j % 10 | '0';
+       tfile = creat(tfname, 0600);
+       if (tfile < 0)
+               goto dumbness;
+       havetmp = 1;
+       close(tfile);
+       tfile = open(tfname, 2);
+       if (tfile < 0)
+               goto dumbness;
+/*     brk((char *)fendcore); */
+}
+
+cleanup(all)
+       bool all;
+{
+       if (all) {
+               putpad(TE);
+               flush();
+       }
+       if (havetmp)
+               unlink(tfname);
+       havetmp = 0;
+       if (all && rfile >= 0) {
+               unlink(rfname);
+               close(rfile);
+               rfile = -1;
+       }
+}
+
+getline(tl)
+       line tl;
+{
+       register char *bp, *lp;
+       register int nl;
+
+       lp = linebuf;
+       bp = getblock(tl, READ);
+       nl = nleft;
+       tl &= ~OFFMSK;
+       while (*lp++ = *bp++)
+               if (--nl == 0) {
+                       bp = getblock(tl += INCRMT, READ);
+                       nl = nleft;
+               }
+}
+
+putline()
+{
+       register char *bp, *lp;
+       register int nl;
+       line tl;
+
+       dirtcnt++;
+       lp = linebuf;
+       change();
+       tl = tline;
+       bp = getblock(tl, WRITE);
+       nl = nleft;
+       tl &= ~OFFMSK;
+       while (*bp = *lp++) {
+               if (*bp++ == '\n') {
+                       *--bp = 0;
+                       linebp = lp;
+                       break;
+               }
+               if (--nl == 0) {
+                       bp = getblock(tl += INCRMT, WRITE);
+                       nl = nleft;
+               }
+       }
+       tl = tline;
+       tline += (((lp - linebuf) + BNDRY - 1) >> SHFT) & 077776;
+       return (tl);
+}
+
+int    read();
+int    write();
+
+char *
+getblock(atl, iof)
+       line atl;
+       int iof;
+{
+       register int bno, off;
+       
+       bno = (atl >> OFFBTS) & BLKMSK;
+       off = (atl << SHFT) & LBTMSK;
+       if (bno >= NMBLKS)
+               error(" Tmp file too large");
+       nleft = BUFSIZ - off;
+       if (bno == iblock) {
+               ichanged |= iof;
+               hitin2 = 0;
+               return (ibuff + off);
+       }
+       if (bno == iblock2) {
+               ichang2 |= iof;
+               hitin2 = 1;
+               return (ibuff2 + off);
+       }
+       if (bno == oblock)
+               return (obuff + off);
+       if (iof == READ) {
+               if (hitin2 == 0) {
+                       if (ichang2)
+                               blkio(iblock2, ibuff2, write);
+                       ichang2 = 0;
+                       iblock2 = bno;
+                       blkio(bno, ibuff2, read);
+                       hitin2 = 1;
+                       return (ibuff2 + off);
+               }
+               hitin2 = 0;
+               if (ichanged)
+                       blkio(iblock, ibuff, write);
+               ichanged = 0;
+               iblock = bno;
+               blkio(bno, ibuff, read);
+               return (ibuff + off);
+       }
+       if (oblock >= 0)
+               blkio(oblock, obuff, write);
+       oblock = bno;
+       return (obuff + off);
+}
+
+blkio(b, buf, iofcn)
+       short b;
+       char *buf;
+       int (*iofcn)();
+{
+
+       lseek(tfile, (long) (unsigned) b * BUFSIZ, 0);
+       if ((*iofcn)(tfile, buf, BUFSIZ) != BUFSIZ)
+               filioerr(tfname);
+}
+
+/*
+ * Synchronize the state of the temporary file in case
+ * a crash occurs.
+ */
+synctmp()
+{
+       register int cnt;
+       register line *a;
+       register short *bp;
+
+       if (dol == zero)
+               return;
+       if (ichanged)
+               blkio(iblock, ibuff, write);
+       ichanged = 0;
+       if (ichang2)
+               blkio(iblock2, ibuff2, write);
+       ichang2 = 0;
+       if (oblock != -1)
+               blkio(oblock, obuff, write);
+       time(&H.Time);
+       uid = getuid();
+       *zero = (line) H.Time;
+       for (a = zero, bp = blocks; a <= dol; a += BUFSIZ / sizeof *a, bp++) {
+               if (*bp < 0) {
+                       tline = (tline + OFFMSK) &~ OFFMSK;
+                       *bp = ((tline >> OFFBTS) & BLKMSK);
+                       tline += INCRMT;
+                       oblock = *bp + 1;
+                       bp[1] = -1;
+               }
+               lseek(tfile, (long) (unsigned) *bp * BUFSIZ, 0);
+               cnt = ((dol - a) + 2) * sizeof (line);
+               if (cnt > BUFSIZ)
+                       cnt = BUFSIZ;
+               if (write(tfile, (char *) a, cnt) != cnt) {
+oops:
+                       *zero = 0;
+                       filioerr(tfname);
+               }
+               *zero = 0;
+       }
+       flines = lineDOL();
+       lseek(tfile, 0l, 0);
+       if (write(tfile, (char *) &H, sizeof H) != sizeof H)
+               goto oops;
+}
+
+TSYNC()
+{
+
+       if (dirtcnt > 12) {
+               dirtcnt = 0;
+               synctmp();
+       }
+}
+
+/*
+ * Named buffer routines.
+ * These are implemented differently than the main buffer.
+ * Each named buffer has a chain of blocks in the register file.
+ * Each block contains roughly 508 chars of text,
+ * and a previous and next block number.  We also have information
+ * about which blocks came from deletes of multiple partial lines,
+ * e.g. deleting a sentence or a LISP object.
+ *
+ * We maintain a free map for the temp file.  To free the blocks
+ * in a register we must read the blocks to find how they are chained
+ * together.
+ *
+ * BUG:                The default savind of deleted lines in numbered
+ *             buffers may be rather inefficient; it hasn't been profiled.
+ */
+struct strreg {
+       short   rg_flags;
+       short   rg_nleft;
+       short   rg_first;
+       short   rg_last;
+} strregs[('z'-'a'+1) + ('9'-'0'+1)], *strp;
+
+struct rbuf {
+       short   rb_prev;
+       short   rb_next;
+       char    rb_text[BUFSIZ - 2 * sizeof (short)];
+} *rbuf;
+short  rused[32];
+short  rnleft;
+short  rblock;
+short  rnext;
+char   *rbufcp;
+
+regio(b, iofcn)
+       short b;
+       int (*iofcn)();
+{
+
+       if (rfile == -1) {
+               CP(rfname, tfname);
+               *(strend(rfname) - 7) = 'R';
+               rfile = creat(rfname, 0600);
+               if (rfile < 0)
+oops:
+                       filioerr(rfname);
+               close(rfile);
+               rfile = open(rfname, 2);
+               if (rfile < 0)
+                       goto oops;
+       }
+       lseek(rfile, (long) b * BUFSIZ, 0);
+       if ((*iofcn)(rfile, rbuf, BUFSIZ) != BUFSIZ)
+               goto oops;
+       rblock = b;
+}
+
+REGblk()
+{
+       register int i, j, m;
+
+       for (i = 0; i < sizeof rused / sizeof rused[0]; i++) {
+               m = (rused[i] ^ 0177777) & 0177777;
+               if (i == 0)
+                       m &= ~1;
+               if (m != 0) {
+                       j = 0;
+                       while ((m & 1) == 0)
+                               j++, m >>= 1;
+                       rused[i] |= (1 << j);
+#ifdef RDEBUG
+                       printf("allocating block %d\n", i * 16 + j);
+#endif
+                       return (i * 16 + j);
+               }
+       }
+       error("Out of register space (ugh)");
+       /*NOTREACHED*/
+}
+
+struct strreg *
+mapreg(c)
+       register int c;
+{
+
+       if (isupper(c))
+               c = tolower(c);
+       return (isdigit(c) ? &strregs[('z'-'a'+1)+(c-'0')] : &strregs[c-'a']);
+}
+
+int    shread();
+
+KILLreg(c)
+       register int c;
+{
+       struct rbuf arbuf;
+       register struct strreg *sp;
+
+       rbuf = &arbuf;
+       sp = mapreg(c);
+       rblock = sp->rg_first;
+       sp->rg_first = sp->rg_last = 0;
+       sp->rg_flags = sp->rg_nleft = 0;
+       while (rblock != 0) {
+#ifdef RDEBUG
+               printf("freeing block %d\n", rblock);
+#endif
+               rused[rblock / 16] &= ~(1 << (rblock % 16));
+               regio(rblock, shread);
+               rblock = rbuf->rb_next;
+       }
+}
+
+/*VARARGS*/
+shread()
+{
+       struct front { short a; short b; };
+
+       if (read(rfile, (char *) rbuf, sizeof (struct front)) == sizeof (struct front))
+               return (sizeof (struct rbuf));
+       return (0);
+}
+
+int    getREG();
+
+putreg(c)
+       char c;
+{
+       struct rbuf arbuf;
+       register line *odot = dot;
+       register line *odol = dol;
+       register int cnt;
+
+       deletenone();
+       appendnone();
+       rbuf = &arbuf;
+       rnleft = 0;
+       rblock = 0;
+       rnext = mapreg(c)->rg_first;
+       if (rnext == 0) {
+               if (inopen) {
+                       splitw++;
+                       vclean();
+                       vgoto(WECHO, 0);
+               }
+               vreg = -1;
+               error("Nothing in register %c", c);
+       }
+       if (inopen && partreg(c)) {
+               squish();
+               addr1 = addr2 = dol;
+       }
+       ignore(append(getREG, addr2));
+       if (inopen && partreg(c)) {
+               unddol = dol;
+               dol = odol;
+               dot = odot;
+               pragged(0);
+       }
+       cnt = undap2 - undap1;
+       killcnt(cnt);
+       notecnt = cnt;
+}
+
+partreg(c)
+       char c;
+{
+
+       return (mapreg(c)->rg_flags);
+}
+
+notpart(c)
+       register int c;
+{
+
+       if (c)
+               mapreg(c)->rg_flags = 0;
+}
+
+getREG()
+{
+       register char *lp = linebuf;
+       register int c;
+
+       for (;;) {
+               if (rnleft == 0) {
+                       if (rnext == 0)
+                               return (EOF);
+                       regio(rnext, read);
+                       rnext = rbuf->rb_next;
+                       rbufcp = rbuf->rb_text;
+                       rnleft = sizeof rbuf->rb_text;
+               }
+               c = *rbufcp;
+               if (c == 0)
+                       return (EOF);
+               rbufcp++, --rnleft;
+               if (c == '\n') {
+                       *lp++ = 0;
+                       return (0);
+               }
+               *lp++ = c;
+       }
+}
+
+YANKreg(c)
+       register int c;
+{
+       struct rbuf arbuf;
+       register line *addr;
+       register struct strreg *sp;
+
+       if (isdigit(c))
+               kshift();
+       if (islower(c))
+               KILLreg(c);
+       strp = sp = mapreg(c);
+       sp->rg_flags = inopen && cursor && wcursor;
+       rbuf = &arbuf;
+       if (sp->rg_last) {
+               regio(sp->rg_last, read);
+               rnleft = sp->rg_nleft;
+               rbufcp = &rbuf->rb_text[sizeof rbuf->rb_text - rnleft];
+       } else {
+               rblock = 0;
+               rnleft = 0;
+       }
+       for (addr = addr1; addr <= addr2; addr++) {
+               getline(*addr);
+               if (sp->rg_flags) {
+                       if (addr == addr2)
+                               *wcursor = 0;
+                       if (addr == addr1)
+                               strcpy(linebuf, cursor);
+               }
+               YANKline();
+       }
+       rbflush();
+       killed();
+}
+
+kshift()
+{
+       register int i;
+
+       KILLreg('9');
+       for (i = '8'; i >= '0'; i--)
+               copy(mapreg(i+1), mapreg(i), sizeof (struct strreg));
+}
+
+YANKline()
+{
+       register char *lp = linebuf;
+       register struct rbuf *rp = rbuf;
+       register int c;
+
+       do {
+               c = *lp++;
+               if (c == 0)
+                       c = '\n';
+               if (rnleft == 0) {
+                       rp->rb_next = REGblk();
+                       rbflush();
+                       rblock = rp->rb_next;
+                       rp->rb_next = 0;
+                       rp->rb_prev = rblock;
+                       rnleft = sizeof rp->rb_text;
+                       rbufcp = rp->rb_text;
+               }
+               *rbufcp++ = c;
+               --rnleft;
+       } while (c != '\n');
+       if (rnleft)
+               *rbufcp = 0;
+}
+
+rbflush()
+{
+       register struct strreg *sp = strp;
+
+       if (rblock == 0)
+               return;
+       regio(rblock, write);
+       if (sp->rg_first == 0)
+               sp->rg_first = rblock;
+       sp->rg_last = rblock;
+       sp->rg_nleft = rnleft;
+}
+
+/* Register c to char buffer buf of size buflen */
+regbuf(c, buf, buflen)
+char c;
+char *buf;
+int buflen;
+{
+       struct rbuf arbuf;
+       register char *p, *lp;
+
+       rbuf = &arbuf;
+       rnleft = 0;
+       rblock = 0;
+       rnext = mapreg(c)->rg_first;
+       if (rnext==0) {
+               *buf = 0;
+               error("Nothing in register %c",c);
+       }
+       p = buf;
+       while (getREG()==0) {
+               for (lp=linebuf; *lp;) {
+                       if (p >= &buf[buflen])
+                               error("Register too long@to fit in memory");
+                       *p++ = *lp++;
+               }
+               *p++ = '\n';
+       }
+       if (partreg(c)) p--;
+       *p = '\0';
+       getDOT();
+}
diff --git a/usr/src/cmd/ex/ex_temp.h b/usr/src/cmd/ex/ex_temp.h
new file mode 100644 (file)
index 0000000..7d4c751
--- /dev/null
@@ -0,0 +1,114 @@
+/* Copyright (c) 1979 Regents of the University of California */
+/*
+ * The editor uses a temporary file for files being edited, in a structure
+ * similar to that of ed.  The first block of the file is used for a header
+ * block which guides recovery after editor/system crashes.
+ * Lines are represented in core by a pointer into the temporary file which
+ * is packed into 16 bits (32 on VMUNIX).  All but the low bit index the temp
+ * file; the last is used by global commands.  The parameters below control
+ * how much the other bits are shifted left before they index the temp file.
+ * Larger shifts give more slop in the temp file but allow larger files
+ * to be edited.
+ *
+ * The editor does not garbage collect the temporary file.  When a new
+ * file is edited, the temporary file is rather discarded and a new one
+ * created for the new file.  Garbage collection would be rather complicated
+ * in ex because of the general undo, and in any case would require more
+ * work when throwing lines away because marks would have be carefully
+ * checked before reallocating temporary file space.  Said another way,
+ * each time you create a new line in the temporary file you get a unique
+ * number back, and this is a property used by marks.
+ *
+ * The following temp file parameters allow 256k bytes in the temporary
+ * file.  By changing to the numbers in comments you can get 512k.
+ * For VMUNIX you get more than you could ever want.
+ * VMUNIX uses long (32 bit) integers giving much more
+ * space in the temp file and no waste.  This doubles core
+ * requirements but allows files of essentially unlimited size to be edited.
+ */
+#ifndef VMUNIX
+#define        BLKMSK  0777            /* 01777 */
+#define        BNDRY   8               /* 16 */
+#define        INCRMT  0200            /* 0100 */
+#define        LBTMSK  0770            /* 0760 */
+#define        NMBLKS  506             /* 1018 */
+#define        OFFBTS  7               /* 6 */
+#define        OFFMSK  0177            /* 077 */
+#define        SHFT    2               /* 3 */
+#else
+#define        BLKMSK  077777
+#define        BNDRY   2
+#define        INCRMT  02000
+#define        LBTMSK  01776
+#define        NMBLKS  077770
+#define        OFFBTS  10
+#define        OFFMSK  01777
+#define        SHFT    0
+#endif
+
+/*
+ * The editor uses three buffers into the temporary file (ed uses two
+ * and is very similar).  These are two read buffers and one write buffer.
+ * Basically, the editor deals with the file as a sequence of BUFSIZ character
+ * blocks.  Each block contains some number of lines (and lines
+ * can run across block boundaries.
+ *
+ * New lines are written into the last block in the temporary file
+ * which is in core as obuf.  When a line is needed which isn't in obuf,
+ * then it is brought into an input buffer.  As there are two, the choice
+ * is to take the buffer into which the last read (of the two) didn't go.
+ * Thus this is a 2 buffer LRU replacement strategy.  Measurement
+ * shows that this saves roughly 25% of the buffer reads over a one
+ * input buffer strategy.  Since the editor (on our VAX over 1 week)
+ * spends (spent) roughly 30% of its time in the system read routine,
+ * this can be a big help.
+ */
+bool   hitin2;                 /* Last read hit was ibuff2 not ibuff */
+bool   ichang2;                /* Have actually changed ibuff2 */
+bool   ichanged;               /* Have actually changed ibuff */
+short  iblock;                 /* Temp file block number of ibuff (or -1) */
+short  iblock2;                /* Temp file block number of ibuff2 (or -1) */
+short  ninbuf;                 /* Number useful chars left in input buffer */
+short  nleft;                  /* Number usable chars left in output buffer */
+short  oblock;                 /* Temp file block number of obuff (or -1) */
+#ifndef VMUNIX
+short  tline;                  /* Current temp file ptr */
+#else
+int    tline;
+#endif
+
+char   ibuff[BUFSIZ];
+char   ibuff2[BUFSIZ];
+char   obuff[BUFSIZ];
+
+/*
+ * Structure of the descriptor block which resides
+ * in the first block of the temporary file and is
+ * the guiding light for crash recovery.
+ *
+ * As the Blocks field below implies, there are temporary file blocks
+ * devoted to (some) image of the incore array of pointers into the temp
+ * file.  Thus, to recover from a crash we use these indices to get the
+ * line pointers back, and then use the line pointers to get the text back.
+ * Except for possible lost lines due to sandbagged I/O, the entire
+ * file (at the time of the last editor "sync") can be recovered from
+ * the temp file.
+ */
+
+/* This definition also appears in expreserve.c... beware */
+struct         header {
+       time_t  Time;                   /* Time temp file last updated */
+       short   Uid;
+#ifndef VMUNIX
+       short   Flines;                 /* Number of lines in file */
+#else
+       int     Flines;
+#endif
+       char    Savedfile[FNSIZE];      /* The current file name */
+       short   Blocks[LBLKS];          /* Blocks where line pointers stashed */
+} H;
+
+#define        uid             H.Uid
+#define        flines          H.Flines
+#define        savedfile       H.Savedfile
+#define        blocks          H.Blocks
diff --git a/usr/src/cmd/ex/ex_tty.c b/usr/src/cmd/ex/ex_tty.c
new file mode 100644 (file)
index 0000000..62763af
--- /dev/null
@@ -0,0 +1,153 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+
+/*
+ * Terminal type initialization routines,
+ * and calculation of flags at entry or after
+ * a shell escape which may change them.
+ */
+short  ospeed = -1;
+
+gettmode()
+{
+
+       if (gtty(1, &tty) < 0)
+               return;
+       if (ospeed != tty.sg_ospeed)
+               value(SLOWOPEN) = tty.sg_ospeed < B1200;
+       ospeed = tty.sg_ospeed;
+       normf = tty.sg_flags;
+       UPPERCASE = (tty.sg_flags & LCASE) != 0;
+       GT = (tty.sg_flags & XTABS) != XTABS && !XT;
+       NONL = (tty.sg_flags & CRMOD) == 0;
+}
+
+char *xPC;
+char **sstrs[] = {
+       &AL, &BC, &BT, &CD, &CE, &CL, &CM, &DC, &DL, &DM, &DO, &ED, &EI,
+       &F0, &F1, &F2, &F3, &F4, &F5, &F6, &F7, &F8, &F9,
+       &HO, &IC, &IM, &IP, &KD, &KE, &KH, &KL, &KR, &KS, &KU, &LL,
+       &ND, &xPC, &SE, &SF, &SO, &SR, &TA, &TE, &TI, &UP, &VB, &VS, &VE
+};
+bool *sflags[] = {
+       &AM, &BS, &DA, &DB, &EO, &HC, &HZ, &IN, &MI, &NC, &OS, &UL, &XN, &XT
+};
+char **fkeys[10] = {
+       &F0, &F1, &F2, &F3, &F4, &F5, &F6, &F7, &F8, &F9
+};
+setterm(type)
+       char *type;
+{
+       char *cgoto();
+       register int unknown, i;
+       register int l;
+       char ltcbuf[TCBUFSIZE];
+
+       if (type[0] == 0)
+               type = "xx";
+       unknown = 0;
+       putpad(TE);
+       if (tgetent(ltcbuf, type) != 1) {
+               unknown++;
+               CP(genbuf, "xx|dumb:");
+       }
+       i = LINES = tgetnum("li");
+       if (LINES <= 5)
+               LINES = 24;
+       if (LINES > 48)
+               LINES = 48;
+       l = LINES;
+       if (ospeed < B1200)
+               l /= 2;
+       else if (ospeed < B2400)
+               l = (l * 2) / 3;
+       aoftspace = tspace;
+       zap();
+       /*
+        * Initialize keypad arrow keys.
+        */
+       arrows[0].cap = KU; arrows[0].mapto = "k"; arrows[0].descr = "up";
+       arrows[1].cap = KD; arrows[1].mapto = "j"; arrows[1].descr = "down";
+       arrows[2].cap = KL; arrows[2].mapto = "h"; arrows[2].descr = "left";
+       arrows[3].cap = KR; arrows[3].mapto = "l"; arrows[3].descr = "right";
+       arrows[4].cap = KH; arrows[4].mapto = "H"; arrows[4].descr = "home";
+
+       options[WINDOW].ovalue = options[WINDOW].odefault = l - 1;
+       if (defwind) options[WINDOW].ovalue = defwind;
+       options[SCROLL].ovalue = options[SCROLL].odefault = HC ? 11 : ((l-1) / 2);
+       COLUMNS = tgetnum("co");
+       if (COLUMNS <= 20)
+               COLUMNS = 1000;
+       if (cgoto()[0] == 'O')  /* OOPS */
+               CA = 0, CM = 0;
+       else
+               CA = 1, costCM = strlen(tgoto(CM, 8, 10));
+       PC = xPC ? xPC[0] : 0;
+       aoftspace = tspace;
+       CP(ttytype, longname(genbuf, type));
+       if (i <= 0)
+               LINES = 2;
+       /* proper strings to change tty type */
+#ifdef notdef
+       /* Taken out because we don't allow it. See ex_set.c for reasons. */
+       if (inopen)
+               putpad(VE);
+#endif
+       termreset();
+       gettmode();
+       value(REDRAW) = AL && DL;
+       value(OPTIMIZE) = !CA && !GT;
+       if (unknown)
+               serror("%s: Unknown terminal type", type);
+}
+
+zap()
+{
+       register char *namp;
+       register bool **fp;
+       register char ***sp;
+
+       namp = "ambsdadbeohchzinmincosulxnxt";
+       fp = sflags;
+       do {
+               *(*fp++) = tgetflag(namp);
+               namp += 2;
+       } while (*namp);
+       namp = "albcbtcdceclcmdcdldmdoedeik0k1k2k3k4k5k6k7k8k9hoicimipkdkekhklkrkskullndpcsesfsosrtatetiupvbvsve";
+       sp = sstrs;
+       do {
+               *(*sp++) = tgetstr(namp, &aoftspace);
+               namp += 2;
+       } while (*namp);
+}
+
+char *
+longname(bp, def)
+       register char *bp;
+       char *def;
+{
+       register char *cp;
+
+       while (*bp && *bp != ':' && *bp != '|')
+               bp++;
+       if (*bp == '|') {
+               bp++;
+               cp = bp;
+               while (*cp && *cp != ':' && *cp != '|')
+                       cp++;
+               *cp = 0;
+               return (bp);
+       }
+       return (def);
+}
+
+char *
+fkey(i)
+       int i;
+{
+       if (0 <= i && i <= 9)
+               return(*fkeys[i]);
+       else
+               return(NOSTR);
+}
diff --git a/usr/src/cmd/ex/ex_tty.h b/usr/src/cmd/ex/ex_tty.h
new file mode 100644 (file)
index 0000000..0dc41fd
--- /dev/null
@@ -0,0 +1,126 @@
+/* Copyright (c) 1979 Regents of the University of California */
+/*
+ * Capabilities from termcap
+ *
+ * The description of terminals is a difficult business, and we only
+ * attempt to summarize the capabilities here;  for a full description
+ * see the paper describing termcap.
+ *
+ * Capabilities from termcap are of three kinds - string valued options,
+ * numeric valued options, and boolean options.  The string valued options
+ * are the most complicated, since they may include padding information,
+ * which we describe now.
+ *
+ * Intelligent terminals often require padding on intelligent operations
+ * at high (and sometimes even low) speed.  This is specified by
+ * a number before the string in the capability, and has meaning for the
+ * capabilities which have a P at the front of their comment.
+ * This normally is a number of milliseconds to pad the operation.
+ * In the current system which has no true programmible delays, we
+ * do this by sending a sequence of pad characters (normally nulls, but
+ * specifiable as "pc").  In some cases, the pad is better computed
+ * as some number of milliseconds times the number of affected lines
+ * (to bottom of screen usually, except when terminals have insert modes
+ * which will shift several lines.)  This is specified as '12*' e.g.
+ * before the capability to say 12 milliseconds per affected whatever
+ * (currently always line).  Capabilities where this makes sense say P*.
+ */
+char   tspace[256];            /* Space for capability strings */
+char   *aoftspace;             /* Address of tspace for relocation */
+
+char   *AL;                    /* P* Add new blank line */
+char   *BC;                    /*    Back cursor */
+char   *BT;                    /* P  Back tab */
+char   *CD;                    /* P* Clear to end of display */
+char   *CE;                    /* P  Clear to end of line */
+char   *CL;                    /* P* Clear screen */
+char   *CM;                    /* P  Cursor motion */
+char   *DC;                    /* P* Delete character */
+char   *DL;                    /* P* Delete line sequence */
+char   *DM;                    /*    Delete mode (enter)  */
+char   *DO;                    /*    Down line sequence */
+char   *ED;                    /*    End delete mode */
+char   *EI;                    /*    End insert mode */
+char   *F0,*F1,*F2,*F3,*F4,*F5,*F6,*F7,*F8,*F9;
+                               /*    Strings sent by various function keys */
+char   *HO;                    /*    Home cursor */
+char   *IC;                    /* P  Insert character */
+char   *IM;                    /*    Insert mode (give as ':im=:' if 'ic' */
+char   *IP;                    /* P* Insert pad after char ins'd using IM+IE */
+char   *KD;                    /*    Keypad down arrow */
+char   *KE;                    /*    Keypad don't xmit */
+char   *KH;                    /*    Keypad home key */
+char   *KL;                    /*    Keypad left arrow */
+char   *KR;                    /*    Keypad right arrow */
+char   *KS;                    /*    Keypad start xmitting */
+char   *KU;                    /*    Keypad up arrow */
+char   *LL;                    /*    Quick to last line, column 0 */
+char   *ND;                    /*    Non-destructive space */
+char   PC;                     /*    Pad character */
+char   *SE;                    /*    Standout end (may leave space) */
+char   *SF;                    /* P  Scroll forwards */
+char   *SO;                    /*    Stand out begin (may leave space) */
+char   *SR;                    /* P  Scroll backwards */
+char   *TA;                    /* P  Tab (other than ^I or with padding) */
+char   *TE;                    /*    Terminal end sequence */
+char   *TI;                    /*    Terminal initial sequence */
+char   *UP;                    /*    Upline */
+char   *VB;                    /*    Visible bell */
+char   *VE;                    /*    Visual end sequence */
+char   *VS;                    /*    Visual start sequence */
+bool   AM;                     /* Automatic margins */
+bool   BS;                     /* Backspace works */
+bool   CA;                     /* Cursor addressible */
+bool   DA;                     /* Display may be retained above */
+bool   DB;                     /* Display may be retained below */
+bool   EO;                     /* Can erase overstrikes with ' ' */
+bool   GT;                     /* Gtty indicates tabs */
+bool   HC;                     /* Hard copy terminal */
+bool   HZ;                     /* Hazeltine ~ braindamage */
+bool   IN;                     /* Insert-null blessing */
+bool   MI;                     /* can move in insert mode */
+bool   NC;                     /* No Cr - \r snds \r\n then eats \n (dm2500) */
+bool   OS;                     /* Overstrike works */
+bool   UL;                     /* Underlining works even though !os */
+bool   XN;                     /* A newline gets eaten after wrap (concept) */
+bool   XT;                     /* Tabs are destructive */
+       /* X? is reserved for severely nauseous glitches */
+       /* If there are enough of these we may need bit masks! */
+
+/*
+ * From the tty modes...
+ */
+bool   NONL;                   /* Terminal can't hack linefeeds doing a CR */
+bool   UPPERCASE;              /* Ick! */
+short  LINES;                  /* Number of lines on screen */
+short  COLUMNS;
+short  OCOLUMNS;               /* Save COLUMNS for a hack in open mode */
+
+short  outcol;                 /* Where the cursor is */
+short  outline;
+
+short  destcol;                /* Where the cursor should be */
+short  destline;
+
+#ifdef         TIOCSETC
+struct tchars ottyc, nttyc;    /* For V7 character masking */
+#endif
+struct sgttyb tty;             /* Always stty/gtty using this one structure */
+bool   normtty;                /* Have to restor normal mode from normf */
+int    normf;                  /* Restore tty flags to this (someday) */
+
+short  WBOT;
+short  WECHO;
+
+short  costCM;
+
+#define MAXNOMACS      32      /* max number of macros */
+#define MAXCHARMACS    512     /* max # of chars total in macros */
+struct maps {
+       char *cap;      /* pressing button that sends this.. */
+       char *mapto;    /* .. maps to this string */
+       char *descr;    /* legible description of key */
+};
+struct maps arrows[MAXNOMACS]; /* macro defs - 1st 5 built in */
+char   mapspace[MAXCHARMACS];
+char   *msnext;        /* next free location in mapspace */
diff --git a/usr/src/cmd/ex/ex_tune.h b/usr/src/cmd/ex/ex_tune.h
new file mode 100644 (file)
index 0000000..192e9b8
--- /dev/null
@@ -0,0 +1,111 @@
+/* Copyright (c) 1979 Regents of the University of California */
+/*
+ * Definitions of editor parameters and limits
+ */
+
+/*
+ * Pathnames.
+ *
+ * Only exstrings is looked at "+4", i.e. if you give
+ * "/usr/lib/..." here, "/lib" will be tried only for strings.
+ */
+#include "local/uparm.h"
+#define        EXRECOVER       libpath(ex3.2recover)
+#define        EXPRESERVE      libpath(ex3.2preserve)
+#ifndef VMUNIX
+#define        EXSTRINGS       libpath(ex3.2strings)
+#endif
+#define        MASTERTAGS      libpath(tags)
+
+/*
+ * If your system believes that tabs expand to a width other than
+ * 8 then your makefile should cc with -DTABS=whatever, otherwise we use 8.
+ */
+#ifndef TABS
+#define        TABS    8
+#endif
+
+/*
+ * Maximums
+ *
+ * The definition of LBSIZE should be the same as BUFSIZ (512 usually).
+ * Most other definitions are quite generous.
+ */
+/* FNSIZE is also defined in expreserve.c */
+#define        FNSIZE          128             /* File name size */
+#ifdef VMUNIX
+#define        LBSIZE          1024
+#define        ESIZE           512
+#else
+#define        LBSIZE          512             /* Line length */
+#define        ESIZE           128             /* Size of compiled re */
+#endif
+#define        RHSSIZE         256             /* Size of rhs of substitute */
+#define        NBRA            9               /* Number of re \( \) pairs */
+#define        TAGSIZE         32              /* Tag length */
+#define        ONMSZ           32              /* Option name size */
+#define        GBSIZE          256             /* Buffer size */
+#define        UXBSIZE         128             /* Unix command buffer size */
+#define        VBSIZE          128             /* Partial line max size in visual */
+/* LBLKS is also defined in expreserve.c */
+#ifndef VMUNIX
+#define        LBLKS           125             /* Line pointer blocks in temp file */
+#define        HBLKS           1               /* struct header fits in BUFSIZ*HBLKS */
+#else
+#define        LBLKS           900
+#define        HBLKS           2
+#endif
+#define        MAXDIRT         12              /* Max dirtcnt before sync tfile */
+#define TCBUFSIZE      1024            /* Max entry size in termcap, see
+                                          also termlib and termcap */
+
+/*
+ * Except on VMUNIX, these are a ridiculously small due to the
+ * lousy arglist processing implementation which fixes core
+ * proportional to them.  Argv (and hence NARGS) is really unnecessary,
+ * and argument character space not needed except when
+ * arguments exist.  Argument lists should be saved before the "zero"
+ * of the incore line information and could then
+ * be reasonably large.
+ */
+#ifndef VMUNIX
+#define        NARGS   100             /* Maximum number of names in "next" */
+#define        NCARGS  LBSIZE          /* Maximum arglist chars in "next" */
+#else
+#define        NCARGS  5120
+#define        NARGS   (NCARGS/6)
+#endif
+
+/*
+ * Note: because the routine "alloca" is not portable, TUBESIZE
+ * bytes are allocated on the stack each time you go into visual
+ * and then never freed by the system.  Thus if you have no terminals
+ * which are larger than 24 * 80 you may well want to make TUBESIZE
+ * smaller.  TUBECOLS should stay at 160 since this defines the maximum
+ * length of opening on hardcopies and allows two lines of open on
+ * terminals like adm3's (glass tty's) where it switches to pseudo
+ * hardcopy mode when a line gets longer than 80 characters.
+ */
+#ifndef VMUNIX
+#define        TUBELINES       40      /* Number of screen lines for visual */
+#define        TUBECOLS        160     /* Number of screen columns for visual */
+#define        TUBESIZE        3400    /* Maximum screen size for visual */
+#else
+#define        TUBELINES       66
+#define        TUBECOLS        160
+#define        TUBESIZE        6600    /* 66 * 100 */
+#endif
+
+/*
+ * Output column (and line) are set to this value on cursor addressible
+ * terminals when we lose track of the cursor to force cursor
+ * addressing to occur.
+ */
+#define        UKCOL           -20     /* Prototype unknown column */
+
+/*
+ * Attention is the interrupt character (normally 0177 -- delete).
+ * Quit is the quit signal (normally FS -- control-\) and quits open/visual.
+ */
+#define        ATTN    0177
+#define        QUIT    ('\\' & 037)
diff --git a/usr/src/cmd/ex/ex_v.c b/usr/src/cmd/ex/ex_v.c
new file mode 100644 (file)
index 0000000..c42808b
--- /dev/null
@@ -0,0 +1,374 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_re.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * Entry points to open and visual from command mode processor.
+ * The open/visual code breaks down roughly as follows:
+ *
+ * ex_v.c      entry points, checking of terminal characteristics
+ *
+ * ex_vadj.c   logical screen control, use of intelligent operations
+ *             insert/delete line and coordination with screen image;
+ *             updating of screen after changes.
+ *
+ * ex_vget.c   input of single keys and reading of input lines
+ *             from the echo area, handling of \ escapes on input for
+ *             uppercase only terminals, handling of memory for repeated
+ *             commands and small saved texts from inserts and partline
+ *             deletes, notification of multi line changes in the echo
+ *             area.
+ *
+ * ex_vmain.c  main command decoding, some command processing.
+ *
+ * ex_voperate.c   decoding of operator/operand sequences and
+ *             contextual scans, implementation of word motions.
+ *
+ * ex_vops.c   major operator interfaces, undos, motions, deletes,
+ *             changes, opening new lines, shifts, replacements and yanks
+ *             coordinating logical and physical changes.
+ *
+ * ex_vops2.c  subroutines for operator interfaces in ex_vops.c,
+ *             insert mode, read input line processing at lowest level.
+ *
+ * ex_vops3.c  structured motion definitions of ( ) { } and [ ] operators,
+ *             indent for lisp routines, () and {} balancing. 
+ *
+ * ex_vput.c   output routines, clearing, physical mapping of logical cursor
+ *             positioning, cursor motions, handling of insert character
+ *             and delete character functions of intelligent and unintelligent
+ *             terminals, visual mode tracing routines (for debugging),
+ *             control of screen image and its updating.
+ *
+ * ex_vwind.c  window level control of display, forward and backward rolls,
+ *             absolute motions, contextual displays, line depth determination
+ */
+
+/*
+ * Enter open mode
+ */
+oop()
+{
+       register char *ic;
+       char atube[TUBESIZE + LBSIZE];
+       register int f;
+
+       ovbeg();
+       if (peekchar() == '/') {
+               ignore(compile(getchar(), 1));
+               savere(scanre);
+               if (execute(0, dot) == 0)
+                       error("Fail|Pattern not found on addressed line");
+               ic = loc1;
+               if (ic > linebuf && *ic == 0)
+                       ic--;
+       } else {
+               getDOT();
+               ic = vskipwh(linebuf);
+       }
+       newline();
+
+       /*
+        * If overstrike then have to HARDOPEN
+        * else if can move cursor up off current line can use CRTOPEN (~~vi1)
+        * otherwise (ugh) have to use ONEOPEN (like adm3)
+        */
+       if (OS && !EO)
+               bastate = HARDOPEN;
+       else if (CA || UP)
+               bastate = CRTOPEN;
+       else
+               bastate = ONEOPEN;
+       setwind();
+
+       /*
+        * To avoid bombing on glass-crt's when the line is too long
+        * pretend that such terminals are 160 columns wide.
+        * If a line is too wide for display, we will dynamically
+        * switch to hardcopy open mode.
+        */
+       if (state != CRTOPEN)
+               WCOLS = TUBECOLS;
+       if (!inglobal)
+               savevis();
+       vok(atube);
+       if (state != CRTOPEN)
+               COLUMNS = WCOLS;
+       Outchar = vputchar;
+       f = ostart();
+       if (state == CRTOPEN) {
+               if (outcol == UKCOL)
+                       outcol = 0;
+               vmoveitup(1, 1);
+       } else
+               outline = destline = WBOT;
+       vshow(dot, NOLINE);
+       vnline(ic);
+       vmain();
+       if (state != CRTOPEN)
+               vclean();
+       Command = "open";
+       ovend(f);
+}
+
+ovbeg()
+{
+
+       if (!value(OPEN))
+               error("Can't use open/visual unless open option is set");
+       if (inopen)
+               error("Recursive open/visual not allowed");
+       Vlines = lineDOL();
+       fixzero();
+       setdot();
+       pastwh();
+       dot = addr2;
+}
+
+ovend(f)
+       int f;
+{
+
+       splitw++;
+       vgoto(WECHO, 0);
+       vclreol();
+       vgoto(WECHO, 0);
+       holdcm = 0;
+       splitw = 0;
+       ostop(f);
+       setoutt();
+       undvis();
+       COLUMNS = OCOLUMNS;
+       inopen = 0;
+       flusho();
+       netchHAD(Vlines);
+}
+
+/*
+ * Enter visual mode
+ */
+vop()
+{
+       register int c;
+       char atube[TUBESIZE + LBSIZE];
+       register int f;
+
+       if (!CA && UP == NOSTR) {
+               if (initev) {
+toopen:
+                       merror("[Using open mode]");
+                       putNFL();
+                       oop();
+                       return;
+               }
+               error("Visual needs addressible cursor or upline capability");
+       }
+       if (OS && !EO) {
+               if (initev)
+                       goto toopen;
+               error("Can't use visual on a terminal which overstrikes");
+       }
+       if (!CL) {
+               if (initev)
+                       goto toopen;
+               error("Visual requires clear screen capability");
+       }
+       ovbeg();
+       bastate = VISUAL;
+       c = 0;
+       if (any(peekchar(), "+-^."))
+               c = getchar();
+       pastwh();
+       vsetsiz(isdigit(peekchar()) ? getnum() : value(WINDOW));
+       setwind();
+       newline();
+       vok(atube);
+       if (!inglobal)
+               savevis();
+       Outchar = vputchar;
+       vmoving = 0;
+       f = ostart();
+       if (initev == 0) {
+               vcontext(dot, c);
+               vnline(NOSTR);
+       }
+       vmain();
+       Command = "visual";
+       ovend(f);
+}
+
+/*
+ * Hack to allow entry to visual with
+ * empty buffer since routines internally
+ * demand at least one line.
+ */
+fixzero()
+{
+
+       if (dol == zero) {
+               register bool ochng = chng;
+
+               vdoappend("");
+               if (!ochng)
+                       sync();
+               addr1 = addr2 = one;
+       } else if (addr2 == zero)
+               addr2 = one;
+}
+
+/*
+ * Save lines before visual between unddol and truedol.
+ * Accomplish this by throwing away current [unddol,truedol]
+ * and then saving all the lines in the buffer and moving
+ * unddol back to dol.  Don't do this if in a global.
+ *
+ * If you do
+ *     g/xxx/vi.
+ * and then do a
+ *     :e xxxx
+ * at some point, and then quit from the visual and undo
+ * you get the old file back.  Somewhat weird.
+ */
+savevis()
+{
+
+       if (inglobal)
+               return;
+       truedol = unddol;
+       saveall();
+       unddol = dol;
+       undkind = UNDNONE;
+}
+
+/*
+ * Restore a sensible state after a visual/open, moving the saved
+ * stuff back to [unddol,dol], and killing the partial line kill indicators.
+ */
+undvis()
+{
+
+       if (ruptible)
+               signal(SIGINT, onintr);
+       squish();
+       pkill[0] = pkill[1] = 0;
+       unddol = truedol;
+       unddel = zero;
+       undap1 = one;
+       undap2 = dol + 1;
+       undkind = UNDALL;
+}
+
+/*
+ * Set the window parameters based on the base state bastate
+ * and the available buffer space.
+ */
+setwind()
+{
+
+       WCOLS = COLUMNS;
+       switch (bastate) {
+
+       case ONEOPEN:
+               if (AM)
+                       WCOLS--;
+               /* fall into ... */
+
+       case HARDOPEN:
+               basWTOP = WTOP = WBOT = WECHO = 0;
+               ZERO = 0;
+               holdcm++;
+               break;
+
+       case CRTOPEN:
+               basWTOP = LINES - 2;
+               /* fall into */
+
+       case VISUAL:
+               ZERO = LINES - TUBESIZE / WCOLS;
+               if (ZERO < 0)
+                       ZERO = 0;
+               if (ZERO > basWTOP)
+                       error("Screen too large for internal buffer");
+               WTOP = basWTOP; WBOT = LINES - 2; WECHO = LINES - 1;
+               break;
+       }
+       state = bastate;
+       basWLINES = WLINES = WBOT - WTOP + 1;
+}
+
+/*
+ * Can we hack an open/visual on this terminal?
+ * If so, then divide the screen buffer up into lines,
+ * and initialize a bunch of state variables before we start.
+ */
+vok(atube)
+       register char *atube;
+{
+       register int i;
+
+       if (WCOLS == 1000)
+               serror("Don't know enough about your terminal to use %s", Command);
+       if (WCOLS > TUBECOLS)
+               error("Terminal too wide");
+       if (WLINES >= TUBELINES || WCOLS * (WECHO - ZERO + 1) > TUBESIZE)
+               error("Screen too large");
+
+       vtube0 = atube;
+       vclrbyte(atube, WCOLS * (WECHO - ZERO + 1));
+       for (i = 0; i < ZERO; i++)
+               vtube[i] = (char *) -20000;
+       for (; i <= WECHO; i++)
+               vtube[i] = atube, atube += WCOLS;
+       for (; i < TUBELINES; i++)
+               vtube[i] = (char *) -20000;
+       vutmp = atube;
+       vundkind = VNONE;
+       vUNDdot = 0;
+       OCOLUMNS = COLUMNS;
+       inopen = 1;
+#ifdef CBREAK
+       signal(SIGINT, vintr);
+#endif
+       vmoving = 0;
+       splitw = 0;
+       doomed = 0;
+       holdupd = 0;
+       Peekkey = 0;
+       vcnt = vcline = 0;
+       if (vSCROLL == 0)
+               vSCROLL = (value(WINDOW)+1)/2;  /* round up so dft=6,11 */
+}
+
+#ifdef CBREAK
+vintr()
+{
+
+       signal(SIGINT, vintr);
+       if (vcatch)
+               onintr();
+       ungetkey(ATTN);
+       draino();
+}
+#endif
+
+/*
+ * Set the size of the screen to size lines, to take effect the
+ * next time the screen is redrawn.
+ */
+vsetsiz(size)
+       int size;
+{
+       register int b;
+
+       if (bastate != VISUAL)
+               return;
+       b = LINES - 1 - size;
+       if (b >= LINES - 1)
+               b = LINES - 2;
+       if (b < 0)
+               b = 0;
+       basWTOP = b;
+       basWLINES = WBOT - b + 1;
+}
diff --git a/usr/src/cmd/ex/ex_vadj.c b/usr/src/cmd/ex/ex_vadj.c
new file mode 100644 (file)
index 0000000..7837339
--- /dev/null
@@ -0,0 +1,1047 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * Routines to deal with management of logical versus physical
+ * display, opening and redisplaying lines on the screen, and
+ * use of intelligent terminal operations.  Routines to deal with
+ * screen cleanup after a change.
+ */
+
+/*
+ * Display a new line at physical line p, returning
+ * the depth of the newly displayed line.  We may decide
+ * to expand the window on an intelligent terminal if it is
+ * less than a full screen by deleting a line above the top of the
+ * window before doing an insert line to keep all the good text
+ * on the screen in which case the line may actually end up
+ * somewhere other than line p.
+ */
+vopen(tp, p)
+       line *tp;
+       int p;
+{
+       register int cnt;
+       register struct vlinfo *vp, *vpc;
+
+#ifdef ADEBUG
+       if (trace != NULL)
+               tfixnl(), fprintf(trace, "vopen(%d, %d)\n", lineno(tp), p);
+#endif
+       if (state != VISUAL) {
+               if (vcnt)
+                       if (hold & HOLDROL)
+                               vup1();
+                       else
+                               vclean();
+
+               /*
+                * Forget all that we once knew.
+                */
+               vcnt = vcline = 0;
+               p = WBOT; LASTLINE = WBOT + 1;
+               state = bastate;
+               WTOP = basWTOP;
+               WLINES = basWLINES;
+       }
+       vpc = &vlinfo[vcline];
+       for (vp = &vlinfo[vcnt]; vp >= vpc; vp--)
+               vlcopy(vp[1], vp[0]);
+       vcnt++;
+       if (Pline == numbline)
+               /*
+                * Dirtying all the lines is rather inefficient
+                * internally, but number mode is used rarely
+                * and so its not worth optimizing.
+                */
+               vdirty(vcline+1, WECHO);
+       getline(*tp);
+
+       /*
+        * If we are opening at the top of the window, can try a window
+        * expansion at the top.
+        */
+       if (state == VISUAL && vcline == 0 && vcnt > 1 && p > ZERO) {
+               cnt = p + vdepth() - LINE(1);
+               if (cnt > 0) {
+                       p -= cnt;
+                       if (p < ZERO)
+                               p = ZERO;
+                       WTOP = p;
+                       WLINES = WBOT - WTOP + 1;
+               }
+       }
+       vpc->vliny = p, vpc->vdepth = 0, vpc->vflags = 0;
+       cnt = vreopen(p, lineno(tp), vcline);
+       if (vcline + 1 == vcnt)
+               LINE(vcnt) = LINE(vcline) + cnt;
+}
+
+/*
+ * Redisplay logical line l at physical line p with line number lineno.
+ */
+vreopen(p, lineno, l)
+       int p, lineno, l;
+{
+       register int d;
+       register struct vlinfo *vp = &vlinfo[l];
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vreopen(%d, %d, %d)\n", p, lineno, l);
+#endif
+       d = vp->vdepth;
+       if (d == 0 || (vp->vflags & VDIRT))
+               vp->vdepth = d = vdepth();
+       vp->vliny = p, vp->vflags &= ~VDIRT;
+
+       /*
+        * Try to win by making the screen larger rather than inserting
+        * a line and driving text off the bottom.
+        */
+       p = vglitchup(l, 0);
+
+       /*
+        * BUG:         Should consider using CE here to clear to end of line.
+        *              As it stands we always strike over the current text.
+        *              Since often the current text is the same as what
+        *              we are overstriking with, it tends not to show.
+        *              On the other hand if it is different and we end up
+        *              spacing out a lot of text, we could have won with
+        *              a CE.  This is probably worthwhile at low speed
+        *              only however, since clearly computation will be
+        *              necessary to determine which way to go.
+        */
+       vigoto(p, 0);
+       pline(lineno);
+
+       /*
+        * When we are typing part of a line for hardcopy open, don't
+        * want to type the '$' marking an end of line if in list mode.
+        */
+       if (hold & HOLDDOL)
+               return (d);
+       if (Putchar == listchar)
+               putchar('$');
+
+       /*
+        * Optimization of cursor motion may prevent screen rollup if the
+        * line has blanks/tabs at the end unless we force the cursor to appear
+        * on the last line segment.
+        */
+       if (vp->vliny + d - 1 > WBOT)
+               vcsync();
+
+       /*
+        * Switch into hardcopy open mode if we are in one line (adm3)
+        * open mode and this line is now too long.  If in hardcopy
+        * open mode, then call sethard to move onto the next line
+        * with appropriate positioning.
+        */
+       if (state == ONEOPEN) {
+               WCOLS = OCOLUMNS;
+               if (vdepth() > 1) {
+                       WCOLS = TUBECOLS;
+                       sethard();
+               } else
+                       WCOLS = TUBECOLS;
+       } else if (state == HARDOPEN)
+               sethard();
+
+       /*
+        * Unless we filled (completely) the last line we typed on,
+        * we have to clear to the end of the line
+        * in case stuff is left from before.
+        */
+       if (vp->vliny + d > destline) {
+               if (IN && destcol == WCOLS)
+                       vigoto(vp->vliny + d - 1, 0);
+               vclreol();
+       }
+       return (d);
+}
+
+/*
+ * Real work for winning growing of window at top
+ * when inserting in the middle of a partially full
+ * screen on an intelligent terminal.  We have as argument
+ * the logical line number to be inserted after, and the offset
+ * from that line where the insert will go.
+ * We look at the picture of depths and positions, and if we can
+ * delete some (blank) lines from the top of the screen so that
+ * later inserts will not push stuff off the bottom.
+ */
+vglitchup(l, o)
+       int l, o;
+{
+       register struct vlinfo *vp = &vlinfo[l];
+       register int need;
+       register int p = vp->vliny;
+       short oldhold, oldheldech;
+       bool glitched = 0;
+
+       if (l < vcnt - 1) {
+               need = p + vp->vdepth - (vp+1)->vliny;
+               if (need > 0) {
+                       if (state == VISUAL && WTOP - ZERO >= need && AL && DL) {
+                               glitched++;
+                               WTOP -= need;
+                               WLINES = WBOT - WTOP + 1;
+                               p -= need;
+                               if (p + o == WTOP) {
+                                       vp->vliny = WTOP;
+                                       return (WTOP + o);
+                               }
+                               vdellin(WTOP, need, -1);
+                               oldheldech = heldech;
+                               oldhold = hold;
+                               hold |= HOLDECH;
+                       }
+                       vinslin((vp+1)->vliny, need, l);
+                       if (glitched) {
+                               hold = oldhold;
+                               heldech = oldheldech;
+                       }
+               }
+       } else
+               vp[1].vliny = vp[0].vliny + vp->vdepth;
+       return (p + o);
+}
+
+/*
+ * Insert cnt blank lines before line p,
+ * logically and (if supported) physically.
+ */
+vinslin(p, cnt, l)
+       register int p, cnt;
+       int l;
+{
+       register int i;
+       bool could = 1;
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vinslin(%d, %d, %d)\n", p, cnt, l);
+#endif
+       if (p + cnt > WBOT && CD) {
+               /*
+                * Really quick -- clear to end of screen.
+                */
+               cnt = WECHO + 1 - p;
+               vgoto(p, 0), vputp(CD, cnt);
+               vclrech(1);
+               vadjAL(p, cnt);
+       } else if (AL) {
+               /*
+                * Use insert line.
+                */
+               vgoto(p, 0), vputp(AL, WECHO + 1 - p);
+               for (i = cnt - 1; i > 0; i--) {
+                       vgoto(outline+1, 0), vputp(AL, WECHO + 1 - outline);
+                       if ((hold & HOLDAT) == 0)
+                               putchar('@');
+               }
+               vadjAL(p, cnt);
+       } else if (SR && p == WTOP) {
+               /*
+                * Use reverse scroll mode of the terminal, at
+                * the top of the window.
+                */
+               for (i = cnt; i > 0; i--) {
+                       vgoto(p, 0), vputp(SR, 0);
+                       if (i > 1 && (hold & HOLDAT) == 0)
+                               putchar('@');
+                       /*
+                        * If we are at the top of the screen, and the
+                        * terminal retains display above, then we
+                        * should try to clear to end of line.
+                        * Have to use CE since we don't remember what is
+                        * actually on the line.
+                        */
+                       if (CE && (DA || p != 0))
+                               vputp(CE, 1);
+               }
+               vadjAL(p, cnt);
+       } else
+               could = 0;
+       vopenup(cnt, could, l);
+}
+
+/*
+ * Logically open up after line l, cnt of them.
+ * We need to know if it was done ``physically'' since in this
+ * case we accept what the hardware gives us.  If we have to do
+ * it ourselves (brute force) we will squish out @ lines in the process
+ * if this will save us work.
+ */
+vopenup(cnt, could, l)
+       int cnt;
+       bool could;
+{
+       register struct vlinfo *vc = &vlinfo[l + 1];
+       register struct vlinfo *ve = &vlinfo[vcnt];
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vopenup(%d, %d, %d)\n", cnt, could, l);
+#endif
+       if (could)
+               /*
+                * This will push @ lines down the screen,
+                * just as the hardware did.  Since the default
+                * for intelligent terminals is to never have @
+                * lines on the screen, this should never happen,
+                * and the code makes no special effort to be nice in this
+                * case, e.g. squishing out the @ lines by delete lines
+                * before doing append lines.
+                */
+               for (; vc <= ve; vc++)
+                       vc->vliny += cnt;
+       else {
+               /*
+                * Will have to clean up brute force eventually,
+                * so push the line data around as little as possible.
+                */
+               vc->vliny += cnt, vc->vflags |= VDIRT;
+               while (vc < ve) {
+                       register int i = vc->vliny + vc->vdepth;
+
+                       vc++;
+                       if (i <= vc->vliny)
+                               break;
+                       vc->vliny = i, vc->vflags |= VDIRT;
+               }
+       }
+       vscrap();
+}
+
+/*
+ * Adjust data structure internally to account for insertion of
+ * blank lines on the screen.
+ */
+vadjAL(p, cnt)
+       int p, cnt;
+{
+       char *tlines[TUBELINES];
+       register int from, to;
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vadjal(%d, %d)\n", p, cnt);
+#endif
+       copy(tlines, vtube, sizeof vtube);      /*SASSIGN*/
+       for (from = p, to = p + cnt; to <= WECHO; from++, to++)
+               vtube[to] = tlines[from];
+       for (to = p; from <= WECHO; from++, to++) {
+               vtube[to] = tlines[from];
+               vclrbyte(vtube[to], WCOLS);
+       }
+       /*
+        * Have to clear the echo area since its contents aren't
+        * necessarily consistent with the rest of the display.
+        */
+       vclrech(0);
+}
+
+/*
+ * Roll the screen up logically and physically
+ * so that line dl is the bottom line on the screen.
+ */
+vrollup(dl)
+       int dl;
+{
+       register int cnt;
+       register int dc = destcol;
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vrollup(%d)\n", dl);
+#endif
+       cnt = dl - (splitw ? WECHO : WBOT);
+       if (splitw && (state == VISUAL || state == CRTOPEN))
+               holdupd = 1;
+       vscroll(cnt);
+       vmoveitup(cnt, 1);
+       destline = dl - cnt, destcol = dc;
+}
+
+vup1()
+{
+
+       vrollup(WBOT + 1);
+}
+
+/*
+ * Scroll the screen up cnt lines physically.
+ * If doclr is true, do a clear eol if the terminal
+ * has standout (to prevent it from scrolling up)
+ */
+vmoveitup(cnt, doclr)
+       register int cnt;
+       bool doclr;
+{
+
+       if (cnt == 0)
+               return;
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vmoveitup(%d)\n", cnt);
+#endif
+       if (doclr && (SO || SE))
+               vclrech(0);
+       if (SF) {
+               while (cnt > 0)
+                       vputp(SF, 0), cnt--;
+               return;
+       }
+       destline = WECHO + cnt;
+       destcol = (NONL ? 0 : outcol % WCOLS);
+       fgoto();
+       if (state == ONEOPEN || state == HARDOPEN) {
+               outline = destline = 0;
+               vclrbyte(vtube[0], WCOLS);
+       }
+}
+
+/*
+ * Scroll the screen up cnt lines logically.
+ */
+vscroll(cnt)
+       register int cnt;
+{
+       register int from, to;
+       char *tlines[TUBELINES];
+
+#ifdef ADEBUG
+       if (trace)
+               fprintf(trace, "vscroll(%d)\n", cnt);
+#endif
+       if (cnt < 0 || cnt > TUBELINES)
+               error("Internal error: vscroll");
+       if (cnt == 0)
+               return;
+       copy(tlines, vtube, sizeof vtube);
+       for (to = ZERO, from = ZERO + cnt; to <= WECHO - cnt; to++, from++)
+               vtube[to] = tlines[from];
+       for (from = ZERO; to <= WECHO; to++, from++) {
+               vtube[to] = tlines[from];
+               vclrbyte(vtube[to], WCOLS);
+       }
+       for (from = 0; from <= vcnt; from++)
+               LINE(from) -= cnt;
+}
+
+/*
+ * Discard logical lines due to physical wandering off the screen.
+ */
+vscrap()
+{
+       register int i, j;
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vscrap\n"), tvliny();
+#endif
+       if (splitw)
+               return;
+       if (vcnt && WBOT != WECHO && LINE(0) < WTOP && LINE(0) >= ZERO) {
+               WTOP = LINE(0);
+               WLINES = WBOT - WTOP + 1;
+       }
+       for (j = 0; j < vcnt; j++)
+               if (LINE(j) >= WTOP) {
+                       if (j == 0)
+                               break;
+                       /*
+                        * Discard the first j physical lines off the top.
+                        */
+                       vcnt -= j, vcline -= j;
+                       for (i = 0; i <= vcnt; i++)
+                               vlcopy(vlinfo[i], vlinfo[i + j]);
+                       break;
+               }
+       /*
+        * Discard lines off the bottom.
+        */
+       if (vcnt) {
+               for (j = 0; j <= vcnt; j++)
+                       if (LINE(j) > WBOT || LINE(j) + DEPTH(j) - 1 > WBOT) {
+                               vcnt = j;
+                               break;
+                       }
+               LASTLINE = LINE(vcnt-1) + DEPTH(vcnt-1);
+       }
+#ifdef ADEBUG
+       if (trace)
+               tvliny();
+#endif
+       /*
+        * May have no lines!
+        */
+}
+
+/*
+ * Repaint the screen, with cursor at curs, aftern an arbitrary change.
+ * Handle notification on large changes.
+ */
+vrepaint(curs)
+       char *curs;
+{
+
+       wdot = NOLINE;
+       /*
+        * In open want to notify first.
+        */
+       noteit(0);
+       vscrap();
+
+       /*
+        * Deal with a totally useless display.
+        */
+       if (vcnt == 0 || vcline < 0 || vcline > vcnt || holdupd && state != VISUAL) {
+               register line *odol = dol;
+
+               vcnt = 0;
+               if (holdupd)
+                       if (state == VISUAL)
+                               ignore(peekkey());
+                       else
+                               vup1();
+               holdupd = 0;
+               if (odol == zero)
+                       fixzero();
+               vcontext(dot, '.');
+               noteit(1);
+               if (noteit(1) == 0 && odol == zero) {
+                       CATCH
+                               error("No lines in buffer");
+                       ENDCATCH
+                       linebuf[0] = 0;
+                       splitw = 0;
+               }
+               vnline(curs);
+               return;
+       }
+
+       /*
+        * Have some useful displayed text; refresh it.
+        */
+       getDOT();
+
+       /*
+        * This is for boundary conditions in open mode.
+        */
+       if (FLAGS(0) & VDIRT)
+               vsync(WTOP);
+       
+       /*
+        * If the current line is after the last displayed line
+        * or the bottom of the screen, then special effort is needed
+        * to get it on the screen.  We first try a redraw at the
+        * last line on the screen, hoping it will fill in where @
+        * lines are now.  If this doesn't work, then roll it onto
+        * the screen.
+        */
+       if (vcline >= vcnt || LINE(vcline) > WBOT) {
+               short oldhold = hold;
+               hold |= HOLDAT, vredraw(LASTLINE), hold = oldhold;
+               if (vcline >= vcnt) {
+                       register int i = vcline - vcnt + 1;
+
+                       dot -= i;
+                       vcline -= i;
+                       vroll(i);
+               } else
+                       vsyncCL();
+       } else
+               vsync(vcline > 0 ? LINE(vcline - 1) : WTOP);
+
+       /*
+        * Notification on large change for visual
+        * has to be done last or we may lose
+        * the echo area with redisplay.
+        */
+       noteit(1);
+
+       /*
+        * Finally.  Move the cursor onto the current line.
+        */
+       vnline(curs);
+}
+
+/*
+ * Fully cleanup the screen, leaving no @ lines except at end when
+ * line after last won't completely fit.  The routine vsync is
+ * more conservative and much less work on dumb terminals.
+ */
+vredraw(p)
+       register int p;
+{
+       register int l;
+       register line *tp;
+       char temp[LBSIZE];
+       bool anydl = 0;
+       short oldhold = hold;
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vredraw(%d)\n", p), tvliny();
+#endif
+       if (holdupd) {
+               holdupd = 3;
+               return;
+       }
+       if (state == HARDOPEN || splitw)
+               return;
+       if (p < 0 /* || p > WECHO */)
+               error("Internal error: vredraw");
+
+       /*
+        * Trim the ragged edges (lines which are off the screen but
+        * not yet logically discarded), save the current line, and
+        * search for first logical line affected by the redraw.
+        */
+       vscrap();
+       CP(temp, linebuf);
+       l = 0;
+       tp = dot - vcline;
+       if (vcnt == 0)
+               LINE(0) = WTOP;
+       while (l < vcnt && LINE(l) < p)
+               l++, tp++;
+
+       /*
+        * We hold off echo area clearing during the redraw in deference
+        * to a final clear of the echo area at the end if appropriate.
+        */
+       heldech = 0;
+       hold |= HOLDECH;
+       for (; l < vcnt && Peekkey != ATTN; l++) {
+               if (l == vcline)
+                       strcLIN(temp);
+               else
+                       getline(*tp);
+
+               /*
+                * Delete junk between displayed lines.
+                */
+               if (LINE(l) != LINE(l + 1) && LINE(l) != p) {
+                       if (anydl == 0 && DB && CD) {
+                               hold = oldhold;
+                               vclrech(0);
+                               anydl = 1;
+                               hold |= HOLDECH;
+                               heldech = 0;
+                       }
+                       vdellin(p, LINE(l) - p, l);
+               }
+
+               /*
+                * If line image is not know to be up to date, then
+                * redisplay it;  else just skip onward.
+                */
+               LINE(l) = p;
+               if (FLAGS(l) & VDIRT) {
+                       DEPTH(l) = vdepth();
+                       if (l != vcline && p + DEPTH(l) - 1 > WBOT) {
+                               vscrap();
+                               break;
+                       }
+                       FLAGS(l) &= ~VDIRT;
+                       vreopen(p, lineno(tp), l);
+                       p = LINE(l) + DEPTH(l);
+               } else
+                       p += DEPTH(l);
+               tp++;
+       }
+
+       /*
+        * That takes care of lines which were already partially displayed.
+        * Now try to fill the rest of the screen with text.
+        */
+       if (state == VISUAL && p <= WBOT) {
+               int ovcline = vcline;
+
+               vcline = l;
+               for (; tp <= dol && Peekkey != ATTN; tp++) {
+                       getline(*tp);
+                       if (p + vdepth() - 1 > WBOT)
+                               break;
+                       vopen(tp, p);
+                       p += DEPTH(vcline);
+                       vcline++;
+               }
+               vcline = ovcline;
+       }
+
+       /*
+        * Thats all the text we can get on.
+        * Now rest of lines (if any) get either a ~ if they
+        * are past end of file, or an @ if the next line won't fit.
+        */
+       for (; p <= WBOT && Peekkey != ATTN; p++)                       
+               vclrlin(p, tp);
+       strcLIN(temp);
+       hold = oldhold;
+       if (heldech)
+               vclrech(0);
+#ifdef ADEBUG
+       if (trace)
+               tvliny();
+#endif
+}
+
+/*
+ * Do the real work in deleting cnt lines starting at line p from
+ * the display.  First affected line is line l.
+ */
+vdellin(p, cnt, l)
+       int p, cnt, l;
+{
+       register int i;
+
+       if (cnt == 0)
+               return;
+       if (DL == NOSTR || cnt < 0) {
+               /*
+                * Can't do it; just remember that line l is munged.
+                */
+               FLAGS(l) |= VDIRT;
+               return;
+       }
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vdellin(%d, %d, %d)\n", p, cnt, l);
+#endif
+       /*
+        * Send the deletes to the screen and then adjust logical
+        * and physical internal data structures.
+        */
+       vgoto(p, 0);
+       for (i = 0; i < cnt; i++)
+               vputp(DL, WECHO - p);
+       vadjDL(p, cnt);
+       vcloseup(l, cnt);
+}
+/*
+ * Adjust internal physical screen image to account for deleted lines.
+ */
+vadjDL(p, cnt)
+       int p, cnt;
+{
+       char *tlines[TUBELINES];
+       register int from, to;
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vadjDL(%d, %d)\n", p, cnt);
+#endif
+       /*
+        * Would like to use structured assignment but early
+        * v7 compiler (released with phototypesetter for v6)
+        * can't hack it.
+        */
+       copy(tlines, vtube, sizeof vtube);      /*SASSIGN*/
+       for (from = p + cnt, to = p; from <= WECHO; from++, to++)
+               vtube[to] = tlines[from];
+       for (from = p; to <= WECHO; from++, to++) {
+               vtube[to] = tlines[from];
+               vclrbyte(vtube[to], WCOLS);
+       }
+}
+/*
+ * Sync the screen, like redraw but more lazy and willing to leave
+ * @ lines on the screen.  VsyncCL syncs starting at the current line.
+ * In any case, if the redraw option is set then all syncs map to redraws
+ * as if vsync didn't exist.
+ */
+vsyncCL()
+{
+
+       vsync(LINE(vcline));
+}
+
+vsync(p)
+       register int p;
+{
+
+       if (value(REDRAW))
+               vredraw(p);
+       else
+               vsync1(p);
+}
+
+/*
+ * The guts of a sync.  Similar to redraw but
+ * just less ambitous.
+ */
+vsync1(p)
+       register int p;
+{
+       register int l;
+       char temp[LBSIZE];
+       register struct vlinfo *vp = &vlinfo[0];
+       short oldhold = hold;
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vsync1(%d)\n", p), tvliny();
+#endif
+       if (holdupd) {
+               if (holdupd < 3)
+                       holdupd = 2;
+               return;
+       }
+       if (state == HARDOPEN || splitw)
+               return;
+       vscrap();
+       CP(temp, linebuf);
+       if (vcnt == 0)
+               LINE(0) = WTOP;
+       l = 0;
+       while (l < vcnt && vp->vliny < p)
+               l++, vp++;
+       heldech = 0;
+       hold |= HOLDECH;
+       while (p <= WBOT && Peekkey != ATTN) {
+               /*
+                * Want to put a line here if not in visual and first line
+                * or if there are lies left and this line starts before
+                * the current line, or if this line is piled under the
+                * next line (vreplace does this and we undo it).
+                */
+               if (l == 0 && state != VISUAL ||
+                   (l < vcnt && (vp->vliny <= p || vp[0].vliny == vp[1].vliny))) {
+                       if (l == 0 || vp->vliny < p || (vp->vflags & VDIRT)) {
+                               if (l == vcline)
+                                       strcLIN(temp);
+                               else
+                                       getline(dot[l - vcline]);
+                               /*
+                                * Be careful that a long line doesn't cause the
+                                * screen to shoot up.
+                                */
+                               if (l != vcline && (vp->vflags & VDIRT)) {
+                                       vp->vdepth = vdepth();
+                                       vp->vflags &= ~VDIRT;
+                                       if (p + vp->vdepth - 1 > WBOT)
+                                               break;
+                               }
+                               vreopen(p, lineDOT() + (l - vcline), l);
+                       }
+                       p = vp->vliny + vp->vdepth;
+                       vp++;
+                       l++;
+               } else
+                       /*
+                        * A physical line between logical lines,
+                        * so we settle for an @ at the beginning.
+                        */
+                       vclrlin(p, dot + (l - vcline)), p++;
+       }
+       strcLIN(temp);
+       hold = oldhold;
+       if (heldech)
+               vclrech(0);
+}
+
+/*
+ * Subtract (logically) cnt physical lines from the 
+ * displayed position of lines starting with line l.
+ */
+vcloseup(l, cnt)
+       int l;
+       register int cnt;
+{
+       register int i;
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vcloseup(%d, %d)\n", l, cnt);
+#endif
+       for (i = l + 1; i <= vcnt; i++)
+               LINE(i) -= cnt;
+}
+
+/*
+ * Workhorse for rearranging line descriptors on changes.
+ * The idea here is that, starting with line l, cnt lines
+ * have been replaced with newcnt lines.  All of these may
+ * be ridiculous, i.e. l may be -1000, cnt 50 and newcnt 0,
+ * since we may be called from an undo after the screen has
+ * moved a lot.  Thus we have to be careful.
+ *
+ * Many boundary conditions here.
+ */
+vreplace(l, cnt, newcnt)
+       int l, cnt, newcnt;
+{
+       register int from, to, i;
+       bool savenote = 0;
+
+#ifdef ADEBUG
+       if (trace) {
+               tfixnl(), fprintf(trace, "vreplace(%d, %d, %d)\n", l, cnt, newcnt);
+               tvliny();
+       }
+#endif
+       if (l >= vcnt)
+               return;
+       if (l < 0) {
+               if (l + cnt < 0) {
+                       /*
+                        * Nothing on the screen is relevant.
+                        * Settle for redrawing from scratch (later).
+                        */
+                       vcnt = 0;
+                       return;
+               }
+               /*
+                * Normalize l to top of screen; the add is
+                * really a subtract from cnt since l is negative.
+                */
+               cnt += l;
+               l = 0;
+
+               /*
+                * Unseen lines were affect so notify (later).
+                */
+               savenote++;
+       }
+
+       /*
+        * These shouldn't happen
+        * but would cause great havoc.
+        */
+       if (cnt < 0)
+               cnt = 0;
+       if (newcnt < 0)
+               newcnt = 0;
+
+       /*
+        * Surely worthy of note if more than report
+        * lines were changed.
+        */
+       if (cnt > value(REPORT) || newcnt > value(REPORT))
+               savenote++;
+
+       /*
+        * Same number of lines affeted as on screen, and we
+        * can insert and delete lines.  Thus we just type
+        * over them, since otherwise we will push them
+        * slowly off the screen, a clear lose.
+        */
+       if (cnt == newcnt || vcnt - l == newcnt && AL && DL) {
+               if (cnt > 1 && l + cnt > vcnt)
+                       savenote++;
+               vdirty(l, newcnt);
+       } else {
+               /*
+                * Lines are going away, squish them out.
+                */
+               if (cnt > 0) {
+                       /*
+                        * If non-displayed lines went away,
+                        * always notify.
+                        */
+                       if (cnt > 1 && l + cnt > vcnt)
+                               savenote++;
+                       if (l + cnt >= vcnt)
+                               cnt = vcnt - l;
+                       else
+                               for (from = l + cnt, to = l; from <= vcnt; to++, from++)
+                                       vlcopy(vlinfo[to], vlinfo[from]);
+                       vcnt -= cnt;
+               }
+               /*
+                * Open up space for new lines appearing.
+                * All new lines are piled in the same place,
+                * and will be unpiled by vredraw/vsync, which
+                * inserts lines in front as it unpiles.
+                */
+               if (newcnt > 0) {
+                       /*
+                        * Newlines are appearing which may not show,
+                        * so notify (this is only approximately correct
+                        * when long lines are present).
+                        */
+                       if (newcnt > 1 && l + newcnt > vcnt + 1)
+                               savenote++;
+
+                       /*
+                        * If there will be more lines than fit, then
+                        * just throw way the rest of the stuff on the screen.
+                        */
+                       if (l + newcnt > WBOT && AL && DL) {
+                               vcnt = l;
+                               goto skip;
+                       }
+                       from = vcnt, to = vcnt + newcnt;
+                       i = TUBELINES - to;
+                       if (i < 0)
+                               from += i, to += i;
+                       vcnt = to;
+                       for (; from >= l; from--, to--)
+                               vlcopy(vlinfo[to], vlinfo[from]);
+                       for (from = to + 1, to = l; to < l + newcnt && to <= WBOT + 1; to++) {
+                               LINE(to) = LINE(from);
+                               DEPTH(to) = 0;
+                               FLAGS(to) = VDIRT;
+                       }
+               }
+       }
+skip:
+       if (Pline == numbline && cnt != newcnt)
+               /*
+                * When lines positions are shifted, the numbers
+                * will be wrong.
+                */
+               vdirty(l, WECHO);
+       if (!savenote)
+               notecnt = 0;
+#ifdef ADEBUG
+       if (trace)
+               tvliny();
+#endif
+}
+
+/*
+ * Start harcopy open.
+ * Print an image of the line to the left of the cursor
+ * under the full print of the line and position the cursor.
+ * If we are in a scroll ^D within hardcopy open then all this
+ * is suppressed.
+ */
+sethard()
+{
+
+       if (state == VISUAL)
+               return;
+       rubble = 0;
+       state = HARDOPEN;
+       if (hold & HOLDROL)
+               return;
+       vup1();
+       LINE(0) = WBOT;
+       if (Pline == numbline)
+               vgoto(WBOT, 0), printf("%6d  ", lineDOT());
+}
+
+/*
+ * Mark the lines starting at base for i lines
+ * as dirty so that they will be checked for correct
+ * display at next sync/redraw.
+ */
+vdirty(base, i)
+       register int base, i;
+{
+       register int l;
+       
+       for (l = base; l < vcnt; l++) {
+               if (--i < 0)
+                       return;
+               FLAGS(l) |= VDIRT;
+       }
+}
diff --git a/usr/src/cmd/ex/ex_vars.h b/usr/src/cmd/ex/ex_vars.h
new file mode 100644 (file)
index 0000000..c3bd859
--- /dev/null
@@ -0,0 +1,37 @@
+#define AUTOINDENT      0
+#define AUTOPRINT       1
+#define AUTOWRITE       2
+#define BEAUTIFY        3
+#define DIRECTORY       4
+#define EDCOMPATIBLE    5
+#define ERRORBELLS      6
+#define HARDTABS        7
+#define IGNORECASE      8
+#define LISP            9
+#define LIST            10
+#define MAGIC           11
+#define MAPINPUT        12
+#define NUMBER          13
+#define OPEN            14
+#define OPTIMIZE        15
+#define PARAGRAPHS      16
+#define PROMPT          17
+#define REDRAW          18
+#define REPORT          19
+#define SCROLL          20
+#define SECTIONS        21
+#define SHELL           22
+#define SHIFTWIDTH      23
+#define SHOWMATCH       24
+#define SLOWOPEN        25
+#define TABSTOP         26
+#define TTYTYPE         27
+#define TERM            28
+#define TERSE           29
+#define WARN            30
+#define WINDOW          31
+#define WRAPSCAN        32
+#define WRAPMARGIN      33
+#define WRITEANY        34
+
+#define        NOPTS   35
diff --git a/usr/src/cmd/ex/ex_vget.c b/usr/src/cmd/ex/ex_vget.c
new file mode 100644 (file)
index 0000000..a4645a0
--- /dev/null
@@ -0,0 +1,546 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * Input routines for open/visual.
+ * We handle upper case only terminals in visual and reading from the
+ * echo area here as well as notification on large changes
+ * which appears in the echo area.
+ */
+
+/*
+ * Return the key.
+ */
+ungetkey(c)
+       char c;
+{
+
+       if (Peekkey != ATTN)
+               Peekkey = c;
+}
+
+/*
+ * Return a keystroke, but never a ^@.
+ */
+getkey()
+{
+       register char c;
+
+       do {
+               c = getbr();
+               if (c==0)
+                       beep();
+       } while (c == 0);
+       return (c);
+}
+
+/*
+ * Tell whether next keystroke would be a ^@.
+ */
+peekbr()
+{
+
+       Peekkey = getbr();
+       return (Peekkey == 0);
+}
+
+short  precbksl;
+
+/*
+ * Get a keystroke, including a ^@.
+ * If an key was returned with ungetkey, that
+ * comes back first.  Next comes unread input (e.g.
+ * from repeating commands with .), and finally new
+ * keystrokes.
+ *
+ * The hard work here is in mapping of \ escaped
+ * characters on upper case only terminals.
+ */
+getbr()
+{
+       char ch;
+       register int c, d;
+       register char *colp;
+
+getATTN:
+       if (Peekkey) {
+               c = Peekkey;
+               Peekkey = 0;
+               return (c);
+       }
+       if (vglobp) {
+               if (*vglobp)
+                       return (lastvgk = *vglobp++);
+               lastvgk = 0;
+               return (ESCAPE);
+       }
+       if (vmacp) {
+               if (*vmacp)
+                       return(*vmacp++);
+               /* End of a macro or set of nested macros */
+               vmacp = 0;
+               if (inopen == -1)       /* don't screw up undo for esc esc */
+                       vundkind = VMANY;
+               inopen = 1;     /* restore old setting now that macro done */
+       }
+#ifdef TRACE
+       if (trace)
+               fflush(trace);
+#endif
+       flusho();
+again:
+       if (read(0, &ch, 1) != 1) {
+               if (errno == EINTR)
+                       goto getATTN;
+               error("Input read error");
+       }
+       c = ch & TRIM;
+
+#ifdef UCVISUAL
+       /*
+        * The algorithm here is that of the UNIX kernel.
+        * See the description in the programmers manual.
+        */
+       if (UPPERCASE) {
+               if (isupper(c))
+                       c = tolower(c);
+               if (c == '\\') {
+                       if (precbksl < 2)
+                               precbksl++;
+                       if (precbksl == 1)
+                               goto again;
+               } else if (precbksl) {
+                       d = 0;
+                       if (islower(c))
+                               d = toupper(c);
+                       else {
+                               colp = "({)}!|^~'~";
+                               while (d = *colp++)
+                                       if (d == c) {
+                                               d = *colp++;
+                                               break;
+                                       } else
+                                               colp++;
+                       }
+                       if (precbksl == 2) {
+                               if (!d) {
+                                       Peekkey = c;
+                                       precbksl = 0;
+                                       c = '\\';
+                               }
+                       } else if (d)
+                               c = d;
+                       else {
+                               Peekkey = c;
+                               precbksl = 0;
+                               c = '\\';
+                       }
+               }
+               if (c != '\\')
+                       precbksl = 0;
+       }
+#endif
+#ifdef TRACE
+       if (trace) {
+               if (!techoin) {
+                       tfixnl();
+                       techoin = 1;
+                       fprintf(trace, "*** Input: ");
+               }
+               tracec(c);
+       }
+#endif
+       lastvgk = 0;
+       return (c);
+}
+
+/*
+ * Get a key, but if a delete, quit or attention
+ * is typed return 0 so we will abort a partial command.
+ */
+getesc()
+{
+       register int c;
+
+       c = getkey();
+       switch (c) {
+
+       case ATTN:
+       case QUIT:
+               ungetkey(c);
+               return (0);
+
+       case ESCAPE:
+               return (0);
+       }
+       return (c);
+}
+
+/*
+ * Peek at the next keystroke.
+ */
+peekkey()
+{
+
+       Peekkey = getkey();
+       return (Peekkey);
+}
+
+/*
+ * Read a line from the echo area, with single character prompt c.
+ * A return value of 1 means the user blewit or blewit away.
+ */
+readecho(c)
+       char c;
+{
+       register char *sc = cursor;
+       register int (*OP)();
+       bool waste;
+       register int OPeek;
+
+       if (WBOT == WECHO)
+               vclean();
+       else
+               vclrech(0);
+       splitw++;
+       vgoto(WECHO, 0);
+       putchar(c);
+       vclreol();
+       vgoto(WECHO, 1);
+       cursor = linebuf; linebuf[0] = 0; genbuf[0] = c;
+       if (peekbr()) {
+               if (!INS[0] || (INS[0] & (QUOTE|TRIM)) == OVERBUF)
+                       goto blewit;
+               vglobp = INS;
+       }
+       OP = Pline; Pline = normline;
+       ignore(vgetline(0, genbuf + 1, &waste));
+       vscrap();
+       Pline = OP;
+       if (Peekkey != ATTN && Peekkey != QUIT && Peekkey != CTRL(h)) {
+               cursor = sc;
+               vclreol();
+               return (0);
+       }
+blewit:
+       OPeek = Peekkey==CTRL(h) ? 0 : Peekkey; Peekkey = 0;
+       splitw = 0;
+       vclean();
+       vshow(dot, NOLINE);
+       vnline(sc);
+       Peekkey = OPeek;
+       return (1);
+}
+
+/*
+ * A complete command has been defined for
+ * the purposes of repeat, so copy it from
+ * the working to the previous command buffer.
+ */
+setLAST()
+{
+
+       if (vglobp)
+               return;
+       lastreg = vreg;
+       lasthad = Xhadcnt;
+       lastcnt = Xcnt;
+       *lastcp = 0;
+       CP(lastcmd, workcmd);
+}
+
+/*
+ * Gather up some more text from an insert.
+ * If the insertion buffer oveflows, then destroy
+ * the repeatability of the insert.
+ */
+addtext(cp)
+       char *cp;
+{
+
+       if (vglobp)
+               return;
+       addto(INS, cp);
+       if ((INS[0] & (QUOTE|TRIM)) == OVERBUF)
+               lastcmd[0] = 0;
+}
+
+setDEL()
+{
+
+       setBUF(DEL);
+}
+
+/*
+ * Put text from cursor upto wcursor in BUF.
+ */
+setBUF(BUF)
+       register char *BUF;
+{
+       register int c;
+       register char *wp = wcursor;
+
+       c = *wp;
+       *wp = 0;
+       BUF[0] = 0;
+       addto(BUF, cursor);
+       *wp = c;
+}
+
+addto(buf, str)
+       register char *buf, *str;
+{
+
+       if ((buf[0] & (QUOTE|TRIM)) == OVERBUF)
+               return;
+       if (strlen(buf) + strlen(str) + 1 >= VBSIZE) {
+               buf[0] = OVERBUF;
+               return;
+       }
+       ignore(strcat(buf, str));
+}
+
+/*
+ * Note a change affecting a lot of lines, or non-visible
+ * lines.  If the parameter must is set, then we only want
+ * to do this for open modes now; return and save for later
+ * notification in visual.
+ */
+noteit(must)
+       bool must;
+{
+       register int sdl = destline, sdc = destcol;
+
+       if (notecnt < 2 || !must && state == VISUAL)
+               return (0);
+       splitw++;
+       if (WBOT == WECHO)
+               vmoveitup(1, 1);
+       vigoto(WECHO, 0);
+       printf("%d %sline", notecnt, notesgn);
+       if (notecnt > 1)
+               putchar('s');
+       if (*notenam) {
+               printf(" %s", notenam);
+               if (*(strend(notenam) - 1) != 'e')
+                       putchar('e');
+               putchar('d');
+       }
+       vclreol();
+       notecnt = 0;
+       if (state != VISUAL)
+               vcnt = vcline = 0;
+       splitw = 0;
+       if (state == ONEOPEN || state == CRTOPEN)
+               vup1();
+       destline = sdl; destcol = sdc;
+       return (1);
+}
+
+/*
+ * Rrrrringgggggg.
+ * If possible, use flash (VB).
+ */
+beep()
+{
+
+       if (VB)
+               vputp(VB, 0);
+       else
+               vputc(CTRL(g));
+}
+
+/*
+ * Map the command input character c,
+ * for keypads and labelled keys which do cursor
+ * motions.  I.e. on an adm3a we might map ^K to ^P.
+ * DM1520 for example has a lot of mappable characters.
+ */
+
+map(c,maps)
+       register int c;
+       register struct maps *maps;
+{
+       register int d;
+       register char *p, *q;
+       char b[10];     /* Assumption: no keypad sends string longer than 10 */
+
+       /*
+        * Mapping for special keys on the terminal only.
+        * BUG: if there's a long sequence and it matches
+        * some chars and then misses, we lose some chars.
+        *
+        * For this to work, some conditions must be met.
+        * 1) Keypad sends SHORT (2 or 3 char) strings
+        * 2) All strings sent are same length & similar
+        * 3) The user is unlikely to type the first few chars of
+        *    one of these strings very fast.
+        * Note: some code has been fixed up since the above was laid out,
+        * so conditions 1 & 2 are probably not required anymore.
+        * However, this hasn't been tested with any first char
+        * that means anything else except escape.
+        */
+#ifdef MDEBUG
+       if (trace)
+               fprintf(trace,"map(%c): ",c);
+#endif
+       b[0] = c;
+       b[1] = 0;
+       for (d=0; maps[d].mapto; d++) {
+#ifdef MDEBUG
+               if (trace)
+                       fprintf(trace,"d=%d, ",d);
+#endif
+               if (p = maps[d].cap) {
+                       for (q=b; *p; p++, q++) {
+#ifdef MDEBUG
+                               if (trace)
+                                       fprintf(trace,"q->b[%d], ",q-b);
+#endif
+                               if (*q==0) {
+                                       /*
+                                        * This test is oversimplified, but
+                                        * should work mostly. It handles the
+                                        * case where we get an ESCAPE that
+                                        * wasn't part of a keypad string.
+                                        */
+                                       if ((c=='#' ? peekkey() : fastpeekkey()) == 0) {
+#ifdef MDEBUG
+                                       if (trace)
+                                               fprintf(trace,"fpk=0: return %c",c);
+#endif
+                                               macpush(&b[1],1);
+                                               return(c);
+                                       }
+                                       *q = getkey();
+                                       q[1] = 0;
+                               }
+                               if (*p != *q)
+                                       goto contin;
+                       }
+                       macpush(maps[d].mapto,1);
+                       c = getkey();
+#ifdef MDEBUG
+       if (trace)
+               fprintf(trace,"Success: return %c",c);
+#endif
+                       return(c);      /* first char of map string */
+                       contin:;
+               }
+       }
+#ifdef MDEBUG
+       if (trace)
+               fprintf(trace,"Fail: return %c",c); /* DEBUG */
+#endif
+       macpush(&b[1],0);
+       return(c);
+}
+
+/*
+ * Push st onto the front of vmacp. This is tricky because we have to
+ * worry about where vmacp was previously pointing. We also have to
+ * check for overflow (which is typically from a recursive macro)
+ * Finally we have to set a flag so the whole thing can be undone.
+ * canundo is 1 iff we want to be able to undo the macro.  This
+ * is false for, for example, pushing back lookahead from fastpeekkey(),
+ * since otherwise two fast escapes can clobber our undo.
+ */
+macpush(st, canundo)
+char *st;
+int canundo;
+{
+       char tmpbuf[BUFSIZ];
+
+       if (st==0 || *st==0)
+               return;
+#ifdef TRACE
+       if (trace)
+               fprintf(trace, "macpush(%s)",st);
+#endif
+       if (strlen(vmacp) + strlen(st) > BUFSIZ)
+               error("Macro too long@ - maybe recursive?");
+       if (vmacp) {
+               strcpy(tmpbuf, vmacp);
+               canundo = 0;    /* can't undo inside a macro anyway */
+       }
+       strcpy(vmacbuf, st);
+       if (vmacp)
+               strcat(vmacbuf, tmpbuf);
+       vmacp = vmacbuf;
+       /* arrange to be able to undo the whole macro */
+       if (canundo) {
+               inopen = -1;    /* no need to save since it had to be 1 or -1 before */
+               otchng = tchng;
+               vsave();
+               saveall();
+               vundkind = VMANY;
+       }
+#ifdef TRACE
+       if (trace)
+               fprintf(trace, "saveall for macro: undkind=%d, unddel=%d, undap1=%d, undap2=%d, dol=%d, unddol=%d, truedol=%d\n", undkind, lineno(unddel), lineno(undap1), lineno(undap2), lineno(dol), lineno(unddol), lineno(truedol));
+#endif
+}
+
+/*
+ * Get a count from the keyed input stream.
+ * A zero count is indistinguishable from no count.
+ */
+vgetcnt()
+{
+       register int c, cnt;
+
+       cnt = 0;
+       for (;;) {
+               c = getkey();
+               if (!isdigit(c))
+                       break;
+               cnt *= 10, cnt += c - '0';
+       }
+       ungetkey(c);
+       Xhadcnt = 1;
+       Xcnt = cnt;
+       return(cnt);
+}
+
+/*
+ * fastpeekkey is just like peekkey but insists the character come in
+ * fast (within 1 second). This will succeed if it is the 2nd char of
+ * a machine generated sequence (such as a function pad from an escape
+ * flavor terminal) but fail for a human hitting escape then waiting.
+ */
+fastpeekkey()
+{
+       int trapalarm();
+       register int c;
+
+       if (inopen == -1)       /* don't work inside macros! */
+               return (0);
+       signal(SIGALRM, trapalarm);
+       alarm(1);
+       CATCH
+               c = peekkey();
+#ifdef MDEBUG
+       if (trace)
+               fprintf(trace,"[OK]",c);
+#endif
+               alarm(0);
+       ONERR
+               c = 0;
+#ifdef MDEBUG
+       if (trace)
+               fprintf(trace,"[TOUT]",c);
+#endif
+       ENDCATCH
+#ifdef MDEBUG
+       if (trace)
+               fprintf(trace,"[fpk:%o]",c);
+#endif
+       return(c);
+}
+
+trapalarm() {
+       alarm(0);
+       longjmp(vreslab,1);
+}
diff --git a/usr/src/cmd/ex/ex_vis.h b/usr/src/cmd/ex/ex_vis.h
new file mode 100644 (file)
index 0000000..9fac3bc
--- /dev/null
@@ -0,0 +1,252 @@
+/* Copyright (c) 1979 Regents of the University of California */
+/*
+ * Ex version 2
+ * Mark Horton, UCB
+ * Bill Joy UCB
+ *
+ * Open and visual mode definitions.
+ * 
+ * There are actually 4 major states in open/visual modes.  These
+ * are visual, crt open (where the cursor can move about the screen and
+ * the screen can scroll and be erased), one line open (on dumb glass-crt's
+ * like the adm3), and hardcopy open (for everything else).
+ *
+ * The basic state is given by bastate, and the current state by state,
+ * since we can be in pseudo-hardcopy mode if we are on an adm3 and the
+ * line is longer than 80.
+ */
+
+short  bastate;
+short  state;
+
+#define        VISUAL          0
+#define        CRTOPEN         1
+#define        ONEOPEN         2
+#define        HARDOPEN        3
+
+/*
+ * The screen in visual and crtopen is of varying size; the basic
+ * window has top basWTOP and basWLINES lines are thereby implied.
+ * The current window (which may have grown from the basic size)
+ * has top WTOP and WLINES lines.  The top line of the window is WTOP,
+ * and the bottom line WBOT.  The line WECHO is used for messages,
+ * search strings and the like.  If WBOT==WECHO then we are in ONEOPEN
+ * or HARDOPEN and there is no way back to the line we were on if we
+ * go to WECHO (i.e. we will have to scroll before we go there, and
+ * we can't get back).  There are WCOLS columns per line.
+ * If WBOT!=WECHO then WECHO will be the last line on the screen
+ * and WBOT is the line before it.
+ */
+short  basWTOP;
+short  basWLINES;
+short  WTOP;
+short  WBOT;
+short  WLINES;
+short  WCOLS;
+short  WECHO;
+
+/*
+ * When we are dealing with the echo area we consider the window
+ * to be "split" and set the variable splitw.  Otherwise, moving
+ * off the bottom of the screen into WECHO causes a screen rollup.
+ */
+bool   splitw;
+
+/*
+ * Information about each line currently on the screen includes
+ * the y coordinate associated with the line, the printing depth
+ * of the line (0 indicates unknown), and a mask which indicates
+ * whether the line is "unclean", i.e. whether we should check
+ * to make sure the line is displayed correctly at the next
+ * appropriate juncture.
+ */
+struct vlinfo {
+       char    vliny;          /* Y coordinate */
+       char    vdepth;         /* Depth of displayed line */
+       short   vflags;         /* Is line potentially dirty ? */
+} vlinfo[TUBELINES + 2];
+
+#define        DEPTH(c)        (vlinfo[c].vdepth)
+#define        LINE(c)         (vlinfo[c].vliny)
+#define        FLAGS(c)        (vlinfo[c].vflags)
+
+#define        VDIRT   1
+
+/*
+ * Hacks to copy vlinfo structures around
+ */
+#ifdef V6
+       /* Kludge to make up for no structure assignment */
+       struct {
+               long    longi;
+       };
+#      define  vlcopy(i, j)    i.longi = j.longi
+#else
+#      define  vlcopy(i, j)    i = j;
+#endif
+
+/*
+ * The current line on the screen is represented by vcline.
+ * There are vcnt lines on the screen, the last being "vcnt - 1".
+ * Vcline is intimately tied to the current value of dot,
+ * and when command mode is used as a subroutine fancy footwork occurs.
+ */
+short  vcline;
+short  vcnt;
+
+/*
+ * To allow many optimizations on output, an exact image of the terminal
+ * screen is maintained in the space addressed by vtube0.  The vtube
+ * array indexes this space as lines, and is shuffled on scrolls, insert+delete
+ * lines and the like rather than (more expensively) shuffling the screen
+ * data itself.  It is also rearranged during insert mode across line
+ * boundaries to make incore work easier.
+ */
+char   *vtube[TUBELINES];
+char   *vtube0;
+
+/*
+ * The current cursor position within the current line is kept in
+ * cursor.  The current line is kept in linebuf.  During insertions
+ * we use the auxiliary array genbuf as scratch area.
+ * The cursor wcursor and wdot are used in operations within/spanning
+ * lines to mark the other end of the affected area, or the target
+ * for a motion.
+ */
+char   *cursor;
+char   *wcursor;
+line   *wdot;
+
+/*
+ * Undo information is saved in a LBSIZE buffer at "vutmp" for changes
+ * within the current line, or as for command mode for multi-line changes
+ * or changes on lines no longer the current line.
+ * The change kind "VCAPU" is used immediately after a U undo to prevent
+ * two successive U undo's from destroying the previous state.
+ */
+#define        VNONE   0
+#define        VCHNG   1
+#define        VMANY   2
+#define        VCAPU   3
+#define        VMCHNG  4
+#define        VMANYINS 5
+
+short  vundkind;       /* Which kind of undo - from above */
+char   *vutmp;         /* Prev line image when "VCHNG" */
+
+/*
+ * For U undo's the line is grabbed by "vmove" after it first appears
+ * on that line.  The "vUNDdot" which specifies which line has been
+ * saved is selectively cleared when changes involving other lines
+ * are made, i.e. after a 'J' join.  This is because a 'JU' would
+ * lose completely the text of the line just joined on.
+ */
+char   *vUNDcurs;      /* Cursor just before 'U' */
+line   *vUNDdot;       /* The line address of line saved in vUNDsav */
+line   vUNDsav;        /* Grabbed initial "*dot" */
+
+#define        killU()         vUNDdot = NOLINE
+
+/*
+ * There are a number of cases where special behaviour is needed
+ * from deeply nested routines.  This is accomplished by setting
+ * the bits of hold, which acts to change the state of the general
+ * visual editing behaviour in specific ways.
+ *
+ * HOLDAT prevents the clreol (clear to end of line) routines from
+ * putting out @'s or ~'s on empty lines.
+ *
+ * HOLDDOL prevents the reopen routine from putting a '$' at the
+ * end of a reopened line in list mode (for hardcopy mode, e.g.).
+ *
+ * HOLDROL prevents spurious blank lines when scrolling in hardcopy
+ * open mode.
+ *
+ * HOLDQIK prevents the fake insert mode during repeated commands.
+ *
+ * HOLDPUPD prevents updating of the physical screen image when
+ * mucking around while in insert mode.
+ *
+ * HOLDECH prevents clearing of the echo area while rolling the screen
+ * backwards (e.g.) in deference to the clearing of the area at the
+ * end of the scroll (1 time instead of n times).  The fact that this
+ * is actually needed is recorded in heldech, which says that a clear
+ * of the echo area was actually held off.
+ */
+short  hold;
+short  holdupd;                /* Hold off update when echo line is too long */
+
+#define        HOLDAT          1
+#define        HOLDDOL         2
+#define        HOLDROL         4
+#define        HOLDQIK         8
+#define        HOLDPUPD        16
+#define        HOLDECH         32
+#define HOLDWIG                64
+
+/*
+ * Miscellaneous variables
+ */
+short  CDCNT;                  /* Count of ^D's in insert on this line */
+char   DEL[VBSIZE];            /* Last deleted text */
+bool   HADUP;                  /* This insert line started with ^ then ^D */
+bool   HADZERO;                /* This insert line started with 0 then ^D */
+char   INS[VBSIZE];            /* Last inserted text */
+int    Vlines;                 /* Number of file lines "before" vi command */
+int    Xcnt;                   /* External variable holding last cmd's count */
+bool   Xhadcnt;                /* Last command had explicit count? */
+short  ZERO;
+short  dir;                    /* Direction for search (+1 or -1) */
+short  doomed;                 /* Disply chars right of cursor to be killed */
+bool   gobblebl;               /* Wrapmargin space generated nl, eat a space */
+bool   hadcnt;                 /* (Almost) internal to vmain() */
+bool   heldech;                /* We owe a clear of echo area */
+bool   insmode;                /* Are in character insert mode */
+char   lastcmd[5];             /* Chars in last command */
+int    lastcnt;                /* Count for last command */
+char   *lastcp;                /* Save current command here to repeat */
+bool   lasthad;                /* Last command had a count? */
+short  lastvgk;                /* Previous input key, if not from keyboard */
+short  lastreg;                /* Register with last command */
+char   *ncols['z'-'a'+2];      /* Cursor positions of marks */
+char   *notenam;               /* Name to be noted with change count */
+char   *notesgn;               /* Change count from last command */
+char   op;                     /* Operation of current command */
+short  Peekkey;                /* Peek ahead key */
+bool   rubble;                 /* Line is filthy (in hardcopy open), redraw! */
+int    vSCROLL;                /* Number lines to scroll on ^D/^U */
+char   *vglobp;                /* Untyped input (e.g. repeat insert text) */
+char   vmacbuf[VBSIZE];        /* Text of visual macro, hence nonnestable */
+char   *vmacp;                 /* Like vglobp but for visual macros */
+char   *vmcurs;                /* Cursor for restore after undo d), e.g. */
+short  vmovcol;                /* Column to try to keep on arrow keys */
+bool   vmoving;                /* Are trying to keep vmovcol */
+char   vreg;                   /* Register for this command */
+short  wdkind;                 /* Liberal/conservative words? */
+char   workcmd[5];             /* Temporary for lastcmd */
+
+
+/*
+ * Macros
+ */
+#define        INF             30000
+#define        LASTLINE        LINE(vcnt)
+#define        OVERBUF         QUOTE
+#define        beep            obeep
+#define        cindent()       ((outline - vlinfo[vcline].vliny) * WCOLS + outcol)
+#define        vputp(cp, cnt)  tputs(cp, cnt, vputch)
+#define        vputc(c)        putch(c)
+
+/*
+ * Function types
+ */
+int    beep();
+int    qcount();
+int    vchange();
+int    vdelete();
+int    vgrabit();
+int    vinschar();
+int    vmove();
+int    vputchar();
+int    vshift();
+int    vyankit();
diff --git a/usr/src/cmd/ex/ex_vmain.c b/usr/src/cmd/ex/ex_vmain.c
new file mode 100644 (file)
index 0000000..e894dee
--- /dev/null
@@ -0,0 +1,1196 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * This is the main routine for visual.
+ * We here decode the count and possible named buffer specification
+ * preceding a command and interpret a few of the commands.
+ * Commands which involve a target (i.e. an operator) are decoded
+ * in the routine operate in ex_voperate.c.
+ */
+
+#define        forbid(a)       { if (a) goto fonfon; }
+
+vmain()
+{
+       register int c, cnt, i;
+       char esave[TUBECOLS];
+       char *oglobp;
+       char d;
+       line *addr;
+       int ind;
+       int onumber, olist, (*OPline)(), (*OPutchar)();
+
+       /*
+        * If we started as a vi command (on the command line)
+        * then go process initial commands (recover, next or tag).
+        */
+       if (initev) {
+               oglobp = globp;
+               globp = initev;
+               hadcnt = cnt = 0;
+               i = tchng;
+               addr = dot;
+               goto doinit;
+       }
+
+       /*
+        * NB:
+        *
+        * The current line is always in the line buffer linebuf,
+        * and the cursor at the position cursor.  You should do
+        * a vsave() before moving off the line to make sure the disk
+        * copy is updated if it has changed, and a getDOT() to get
+        * the line back if you mung linebuf.  The motion
+        * routines in ex_vwind.c handle most of this.
+        */
+       for (;;) {
+               /*
+                * Decode a visual command.
+                * First sync the temp file if there has been a reasonable
+                * amount of change.  Clear state for decoding of next
+                * command.
+                */
+               TSYNC();
+               vglobp = 0;
+               vreg = 0;
+               hold = 0;
+               wcursor = 0;
+               Xhadcnt = hadcnt = 0;
+               Xcnt = cnt = 1;
+               splitw = 0;
+               if (i = holdupd) {
+                       if (state == VISUAL)
+                               ignore(peekkey());
+                       holdupd = 0;
+/*
+                       if (LINE(0) < ZERO) {
+                               vclear();
+                               vcnt = 0;
+                               i = 3;
+                       }
+*/
+                       if (state != VISUAL) {
+                               vcnt = 0;
+                               vsave();
+                               vrepaint(cursor);
+                       } else if (i == 3)
+                               vredraw(WTOP);
+                       else
+                               vsync(WTOP);
+                       vfixcurs();
+               }
+
+               /*
+                * Gobble up counts and named buffer specifications.
+                */
+               for (;;) {
+looptop:
+#ifdef MDEBUG
+                       if (trace)
+                               fprintf(trace, "pc=%c",peekkey());
+#endif
+                       if (isdigit(peekkey()) && peekkey() != '0') {
+                               hadcnt = 1;
+                               cnt = vgetcnt();
+                               forbid (cnt <= 0);
+                       }
+                       if (peekkey() != '"')
+                               break;
+                       ignore(getkey()), c = getkey();
+                       /*
+                        * Buffer names be letters or digits.
+                        * But not '0' as that is the source of
+                        * an 'empty' named buffer spec in the routine
+                        * kshift (see ex_temp.c).
+                        */
+                       forbid (c == '0' || !isalpha(c) && !isdigit(c));
+                       vreg = c;
+               }
+reread:
+               /*
+                * Come to reread from below after some macro expansions.
+                * The call to map allows use of function key pads
+                * by performing a terminal dependent mapping of inputs.
+                */
+#ifdef MDEBUG
+               if (trace)
+                       fprintf(trace,"pcb=%c,",peekkey());
+#endif
+               op = getkey();
+               do {
+                       /*
+                        * Keep mapping the char as long as it changes.
+                        * This allows for double mappings, e.g., q to #,
+                        * #1 to something else.
+                        */
+                       c = op;
+                       op = map(c,arrows);
+#ifdef MDEBUG
+                       if (trace)
+                               fprintf(trace,"pca=%c,",c);
+#endif
+                       /*
+                        * Maybe the mapped to char is a count. If so, we have
+                        * to go back to the "for" to interpret it. Likewise
+                        * for a buffer name.
+                        */
+                       if ((isdigit(c) && c!='0') || c == '"') {
+                               ungetkey(c);
+                               goto looptop;
+                       }
+               } while (c != op);
+
+               /*
+                * Begin to build an image of this command for possible
+                * later repeat in the buffer workcmd.  It will be copied
+                * to lastcmd by the routine setLAST
+                * if/when completely specified.
+                */
+               lastcp = workcmd;
+               if (!vglobp)
+                       *lastcp++ = c;
+
+               /*
+                * First level command decode.
+                */
+               switch (c) {
+
+               /*
+                * ^L           Clear screen e.g. after transmission error.
+                */
+               case CTRL(l):
+                       vclear();
+                       vdirty(0, vcnt);
+                       /* fall into... */
+
+               /*
+                * ^R           Retype screen, getting rid of @ lines.
+                *              If in open, equivalent to ^L.
+                */
+               case CTRL(r):
+                       if (state != VISUAL) {
+                               /*
+                                * Get a clean line, throw away the
+                                * memory of what is displayed now,
+                                * and move back onto the current line.
+                                */
+                               vclean();
+                               vcnt = 0;
+                               vmoveto(dot, cursor, 0);
+                               continue;
+                       }
+                       vredraw(WTOP);
+                       /*
+                        * Weird glitch -- when we enter visual
+                        * in a very small window we may end up with
+                        * no lines on the screen because the line
+                        * at the top is too long.  This forces the screen
+                        * to be expanded to make room for it (after
+                        * we have printed @'s ick showing we goofed).
+                        */
+                       if (vcnt == 0)
+                               vrepaint(cursor);
+                       vfixcurs();
+                       continue;
+
+               /*
+                * $            Escape just cancels the current command
+                *              with a little feedback.
+                */
+               case ESCAPE:
+                       beep();
+                       continue;
+
+               /*
+                * @            Macros. Bring in the macro and put it
+                *              in vmacbuf, point vglobp there and punt.
+                */
+                case '@':
+                       c = getkey();
+                       if (c == '@')
+                               c = lastmac;
+                       if (isupper(c))
+                               c = tolower(c);
+                       forbid(!islower(c));
+                       lastmac = c;
+                       vsave();
+                       CATCH
+                               char tmpbuf[BUFSIZ];
+
+                               regbuf(c,tmpbuf,sizeof(vmacbuf));
+                               macpush(tmpbuf);
+                       ONERR
+                               lastmac = 0;
+                               splitw = 0;
+                               getDOT();
+                               vrepaint(cursor);
+                               continue;
+                       ENDCATCH
+                       vmacp = vmacbuf;
+                       goto reread;
+
+               /*
+                * .            Repeat the last (modifying) open/visual command.
+                */
+               case '.':
+                       /*
+                        * Check that there was a last command, and
+                        * take its count and named buffer unless they
+                        * were given anew.  Special case if last command
+                        * referenced a numeric named buffer -- increment
+                        * the number and go to a named buffer again.
+                        * This allows a sequence like "1pu.u.u...
+                        * to successively look for stuff in the kill chain
+                        * much as one does in EMACS with C-Y and M-Y.
+                        */
+                       forbid (lastcmd[0] == 0);
+                       if (hadcnt)
+                               lastcnt = cnt;
+                       if (vreg)
+                               lastreg = vreg;
+                       else if (isdigit(lastreg) && lastreg < '9')
+                               lastreg++;
+                       vreg = lastreg;
+                       cnt = lastcnt;
+                       hadcnt = lasthad;
+                       vglobp = lastcmd;
+                       goto reread;
+
+               /*
+                * ^U           Scroll up.  A count sticks around for
+                *              future scrolls as the scroll amount.
+                *              Attempt to hold the indentation from the
+                *              top of the screen (in logical lines).
+                *
+                * BUG:         A ^U near the bottom of the screen
+                *              on a dumb terminal (which can't roll back)
+                *              causes the screen to be cleared and then
+                *              redrawn almost as it was.  In this case
+                *              one should simply move the cursor.
+                */
+               case CTRL(u):
+                       if (hadcnt)
+                               vSCROLL = cnt;
+                       cnt = vSCROLL;
+                       if (state == VISUAL)
+                               ind = vcline, cnt += ind;
+                       else
+                               ind = 0;
+                       vmoving = 0;
+                       vup(cnt, ind, 1);
+                       vnline(NOSTR);
+                       continue;
+
+               /*
+                * ^D           Scroll down.  Like scroll up.
+                */
+               case CTRL(d):
+                       if (hadcnt)
+                               vSCROLL = cnt;
+                       cnt = vSCROLL;
+                       if (state == VISUAL)
+                               ind = vcnt - vcline - 1, cnt += ind;
+                       else
+                               ind = 0;
+                       vmoving = 0;
+                       vdown(cnt, ind, 1);
+                       vnline(NOSTR);
+                       continue;
+
+               /*
+                * ^E           Glitch the screen down (one) line.
+                *              Cursor left on same line in file.
+                */
+               case CTRL(e):
+                       if (state != VISUAL)
+                               continue;
+                       if (!hadcnt)
+                               cnt = 1;
+                       /* Bottom line of file already on screen */
+                       forbid(lineDOL()-lineDOT() <= vcnt-1-vcline);
+                       ind = vcnt - vcline - 1 + cnt;
+                       vdown(ind, ind, 1);
+                       vnline(cursor);
+                       continue;
+
+               /*
+                * ^Y           Like ^E but up
+                */
+               case CTRL(y):
+                       if (state != VISUAL)
+                               continue;
+                       if (!hadcnt)
+                               cnt = 1;
+                       forbid(lineDOT()-1<=vcline); /* line 1 already there */
+                       ind = vcline + cnt;
+                       vup(ind, ind, 1);
+                       vnline(cursor);
+                       continue;
+
+
+               /*
+                * m            Mark position in mark register given
+                *              by following letter.  Return is
+                *              accomplished via ' or `; former
+                *              to beginning of line where mark
+                *              was set, latter to column where marked.
+                */
+               case 'm':
+                       /*
+                        * Getesc is generally used when a character
+                        * is read as a latter part of a command
+                        * to allow one to hit rubout/escape to cancel
+                        * what you have typed so far.  These characters
+                        * are mapped to 0 by the subroutine.
+                        */
+                       c = getesc();
+                       if (c == 0)
+                               continue;
+
+                       /*
+                        * Markreg checks that argument is a letter
+                        * and also maps ' and ` to the end of the range
+                        * to allow '' or `` to reference the previous
+                        * context mark.
+                        */
+                       c = markreg(c);
+                       forbid (c == 0);
+                       vsave();
+                       names[c - 'a'] = (*dot &~ 01);
+                       ncols[c - 'a'] = cursor;
+                       anymarks = 1;
+                       continue;
+
+               /*
+                * ^F           Window forwards, with 2 lines of continuity.
+                *              Count gives new screen size.
+                */
+               case CTRL(f):
+                       vsave();
+                       if (hadcnt)
+                               vsetsiz(cnt);
+                       if (vcnt > 2) {
+                               dot += (vcnt - vcline) - 2;
+                               vcnt = vcline = 0;
+                       }
+                       vzop(0, 0, '+');
+                       continue;
+
+               /*
+                * ^B           Window backwards, with 2 lines of continuity.
+                *              Inverse of ^F.
+                */
+               case CTRL(b):
+                       vsave();
+                       if (hadcnt)
+                               vsetsiz(cnt);
+                       if (one + vcline != dot && vcnt > 2) {
+                               dot -= vcline - 2;
+                               vcnt = vcline = 0;
+                       }
+                       vzop(0, 0, '^');
+                       continue;
+
+               /*
+                * z            Screen adjustment, taking a following character:
+                *                      z<CR>           current line to top
+                *                      z<NL>           like z<CR>
+                *                      z-              current line to bottom
+                *              also z+, z^ like ^F and ^B.
+                *              A preceding count is line to use rather
+                *              than current line.  A count between z and
+                *              specifier character changes the screen size
+                *              for the redraw.
+                *
+                */
+               case 'z':
+                       if (state == VISUAL) {
+                               i = vgetcnt();
+                               if (i > 0)
+                                       vsetsiz(i);
+                               c = getesc();
+                               if (c == 0)
+                                       continue;
+                       }
+                       vsave();
+                       vzop(hadcnt, cnt, c);
+                       continue;
+
+               /*
+                * Y            Yank lines, abbreviation for y_ or yy.
+                *              Yanked lines can be put later if no
+                *              changes intervene, or can be put in named
+                *              buffers and put anytime in this session.
+                */
+               case 'Y':
+                       ungetkey('_');
+                       c = 'y';
+                       break;
+
+               /*
+                * J            Join lines, 2 by default.  Count is number
+                *              of lines to join (no join operator sorry.)
+                */
+               case 'J':
+                       forbid (dot == dol);
+                       if (cnt == 1)
+                               cnt = 2;
+                       if (cnt > (i = dol - dot + 1))
+                               cnt = i;
+                       vsave();
+                       setLAST();
+                       cursor = strend(linebuf);
+                       vremote(cnt, join, 0);
+                       notenam = "join";
+                       vmoving = 0;
+                       killU();
+                       vreplace(vcline, cnt, 1);
+                       if (!*cursor && cursor > linebuf)
+                               cursor--;
+                       if (notecnt == 2)
+                               notecnt = 0;
+                       vrepaint(cursor);
+                       continue;
+
+               /*
+                * S            Substitute text for whole lines, abbrev for c_.
+                *              Count is number of lines to change.
+                */
+               case 'S':
+                       ungetkey('_');
+                       c = 'c';
+                       break;
+
+               /*
+                * O            Create a new line above current and accept new
+                *              input text, to an escape, there.
+                *              A count specifies, for dumb terminals when
+                *              slowopen is not set, the number of physical
+                *              line space to open on the screen.
+                *
+                * o            Like O, but opens lines below.
+                */
+               case 'O':
+               case 'o':
+                       voOpen(c, cnt);
+                       continue;
+
+               /*
+                * C            Change text to end of line, short for c$.
+                */
+               case 'C':
+                       if (*cursor) {
+                               ungetkey('$'), c = 'c';
+                               break;
+                       }
+                       goto appnd;
+
+               /*
+                * ~    Switch case of letter under cursor
+                */
+               case '~':
+                       {
+                               char mbuf[4];
+                               mbuf[0] = 'r';
+                               mbuf[1] = *cursor;
+                               mbuf[2] = cursor[1]==0 ? 0 : ' ';
+                               mbuf[3] = 0;
+                               if (isalpha(mbuf[1]))
+                                       mbuf[1] ^= ' '; /* toggle the case */
+                               macpush(mbuf);
+                       }
+                       continue;
+
+
+               /*
+                * A            Append at end of line, short for $a.
+                */
+               case 'A':
+                       operate('$', 1);
+appnd:
+                       c = 'a';
+                       /* fall into ... */
+
+               /*
+                * a            Appends text after cursor.  Text can continue
+                *              through arbitrary number of lines.
+                */
+               case 'a':
+                       if (*cursor) {
+                               if (state == HARDOPEN)
+                                       putchar(*cursor);
+                               cursor++;
+                       }
+                       goto insrt;
+
+               /*
+                * I            Insert at beginning of whitespace of line,
+                *              short for ^i.
+                */
+               case 'I':
+                       operate('^', 1);
+                       c = 'i';
+                       /* fall into ... */
+
+               /*
+                * R            Replace characters, one for one, by input
+                *              (logically), like repeated r commands.
+                *
+                * BUG:         This is like the typeover mode of many other
+                *              editors, and is only rarely useful.  Its
+                *              implementation is a hack in a low level
+                *              routine and it doesn't work very well, e.g.
+                *              you can't move around within a R, etc.
+                */
+               case 'R':
+                       /* fall into... */
+
+               /*
+                * i            Insert text to an escape in the buffer.
+                *              Text is arbitrary.  This command reminds of
+                *              the i command in bare teco.
+                */
+               case 'i':
+insrt:
+                       /*
+                        * Common code for all the insertion commands.
+                        * Save for redo, position cursor, prepare for append
+                        * at command and in visual undo.  Note that nothing
+                        * is doomed, unless R when all is, and save the
+                        * current line in a the undo temporary buffer.
+                        */
+                       setLAST();
+                       vcursat(cursor);
+                       prepapp();
+                       vnoapp();
+                       doomed = c == 'R' ? 10000 : 0;
+                       vundkind = VCHNG;
+                       CP(vutmp, linebuf);
+
+                       /*
+                        * If this is a repeated command, then suppress
+                        * fake insert mode on dumb terminals which looks
+                        * ridiculous and wastes lots of time even at 9600B.
+                        */
+                       if (vglobp)
+                               hold = HOLDQIK;
+                       vappend(c, cnt, 0);
+                       continue;
+
+               /*
+                * ^?           An attention, normally a ^?, just beeps.
+                *              If you are a vi command within ex, then
+                *              two ATTN's will drop you back to command mode.
+                */
+               case ATTN:
+                       beep();
+                       if (initev || peekkey() != ATTN)
+                               continue;
+                       /* fall into... */
+
+               /*
+                * ^\           A quit always gets command mode.
+                */
+               case QUIT:
+                       /*
+                        * Have to be careful if we were called
+                        *      g/xxx/vi
+                        * since a return will just start up again.
+                        * So we simulate an interrupt.
+                        */
+                       if (inglobal)
+                               onintr();
+                       /* fall into... */
+
+               /*
+                * q            Quit back to command mode, unless called as
+                *              vi on command line in which case dont do it
+                */
+               case 'q':       /* quit */
+                       if (initev) {
+                               vsave();
+                               CATCH
+                                       error("Q gets ex command mode, :q leaves vi");
+                               ENDCATCH
+                               splitw = 0;
+                               getDOT();
+                               vrepaint(cursor);
+                               continue;
+                       }
+                       /* fall into... */
+
+               /*
+                * Q            Is like q, but always gets to command mode
+                *              even if command line invocation was as vi.
+                */
+               case 'Q':
+                       vsave();
+                       return;
+
+               /*
+                * P            Put back text before cursor or before current
+                *              line.  If text was whole lines goes back
+                *              as whole lines.  If part of a single line
+                *              or parts of whole lines splits up current
+                *              line to form many new lines.
+                *              May specify a named buffer, or the delete
+                *              saving buffers 1-9.
+                *
+                * p            Like P but after rather than before.
+                */
+               case 'P':
+               case 'p':
+                       vmoving = 0;
+                       forbid (inopen < 0);
+                       /*
+                        * If previous delete was partial line, use an
+                        * append or insert to put it back so as to
+                        * use insert mode on intelligent terminals.
+                        */
+                       if (!vreg && DEL[0]) {
+                               forbid ((DEL[0] & (QUOTE|TRIM)) == OVERBUF);
+                               vglobp = DEL;
+                               ungetkey(c == 'p' ? 'a' : 'i');
+                               goto reread;
+                       }
+
+                       /*
+                        * If a register wasn't specified, then make
+                        * sure there is something to put back.
+                        */
+                       forbid (!vreg && unddol == dol);
+                       vsave();
+                       setLAST();
+                       i = 0;
+                       if (vreg && partreg(vreg) || !vreg && pkill[0]) {
+                               /*
+                                * Restoring multiple lines which were partial
+                                * lines; will leave cursor in middle
+                                * of line after shoving restored text in to
+                                * split the current line.
+                                */
+                               i++;
+                               if (c == 'p' && *cursor)
+                                       cursor++;
+                       } else {
+                               /*
+                                * In whole line case, have to back up dot
+                                * for P; also want to clear cursor so
+                                * cursor will eventually be positioned
+                                * at the beginning of the first put line.
+                                */
+                               cursor = 0;
+                               if (c == 'P') {
+                                       dot--, vcline--;
+                                       c = 'p';
+                               }
+                       }
+                       killU();
+
+                       /*
+                        * The call to putreg can potentially
+                        * bomb since there may be nothing in a named buffer.
+                        * We thus put a catch in here.  If we didn't and
+                        * there was an error we would end up in command mode.
+                        */
+                       CATCH
+                               vremote(1, vreg ? putreg : put, vreg);
+                       ONERR
+                               if (vreg == -1) {
+                                       splitw = 0;
+                                       if (op == 'P')
+                                               dot++, vcline++;
+                                       goto pfixup;
+                               }
+                       ENDCATCH
+                       splitw = 0;
+                       if (!i) {
+                               /*
+                                * Increment undap1, undap2 to make up
+                                * for their incorrect initialization in the
+                                * routine vremote before calling put/putreg.
+                                */
+                               undap1++, undap2++;
+                               vcline++;
+                       }
+
+                       /*
+                        * After a put want current line first line,
+                        * and dot was made the last line put in code run
+                        * so far.  This is why we increment vcline above,
+                        * and decrease (usually) dot here.
+                        */
+                       dot = undap1;
+                       vreplace(vcline, i, undap2 - undap1);
+                       if (state != VISUAL) {
+                               /*
+                                * Special case in open mode.
+                                * Force action on the screen when a single
+                                * line is put even if it is identical to
+                                * the current line, e.g. on YP; otherwise
+                                * you can't tell anything happened.
+                                */
+                               vjumpto(dot, cursor, '.');
+                               continue;
+                       }
+pfixup:
+                       vrepaint(cursor);
+                       vfixcurs();
+                       continue;
+
+               /*
+                * ^^           Return to previous context.  Like a 't
+                *              if that mark is set since tag sets that
+                *              mark if it stays in same file.  Else
+                *              like a :e #, and thus can be used after a
+                *              "No Write" diagnostic.
+                *
+                *              Note: this doesn't correspond with documentation
+                *              Is this comment misleading?
+                */
+               case CTRL(^):
+                       if (hadcnt)
+                               vsetsiz(cnt);
+                       addr = getmark('t');
+                       if (addr != 0) {
+                               markit(addr);
+                               vupdown(addr - dot, NOSTR);
+                               continue;
+                       }
+                       vsave();
+                       ckaw();
+                       oglobp = globp;
+                       if (value(AUTOWRITE))
+                               globp = "e! #";
+                       else
+                               globp = "e #";
+                       goto gogo;
+
+               /*
+                * ^]           Takes word after cursor as tag, and then does
+                *              tag command.  Read ``go right to''.
+                */
+               case CTRL(]):
+                       grabtag();
+                       oglobp = globp;
+                       globp = "tag";
+                       goto gogo;
+
+               /*
+                * &            Like :&
+                */
+                case '&':
+                       oglobp = globp;
+                       globp = "&";
+                       goto gogo;
+                       
+               /*
+                * ^G           Bring up a status line at the bottom of
+                *              the screen, like a :file command.
+                *
+                * BUG:         Was ^S but doesn't work in cbreak mode
+                */
+               case CTRL(g):
+                       oglobp = globp;
+                       globp = "file";
+gogo:
+                       addr = dot;
+                       vsave();
+                       goto doinit;
+
+               /*
+                * :            Read a command from the echo area and
+                *              execute it in command mode.
+                */
+               case ':':
+                       if (hadcnt)
+                               vsetsiz(cnt);
+                       vsave();
+                       i = tchng;
+                       addr = dot;
+                       if (readecho(c)) {
+                               esave[0] = 0;
+                               goto fixup;
+                       }
+                       /*
+                        * Use the visual undo buffer to store the global
+                        * string for command mode, since it is idle right now.
+                        */
+                       oglobp = globp; strcpy(vutmp, genbuf+1); globp = vutmp;
+doinit:
+                       esave[0] = 0;
+                       fixech();
+
+                       /*
+                        * Have to finagle around not to lose last
+                        * character after this command (when run from ex
+                        * command mode).  This is clumsy.
+                        */
+                       d = peekc; ungetchar(0);
+                       CATCH
+                               /*
+                                * Save old values of options so we can
+                                * notice when they change; switch into
+                                * cooked mode so we are interruptible.
+                                */
+                               onumber = value(NUMBER);
+                               olist = value(LIST);
+                               OPline = Pline;
+                               OPutchar = Putchar;
+#ifndef CBREAK
+                               vcook();
+#endif
+                               commands(1, 1);
+                               if (dot == zero && dol > zero)
+                                       dot = one;
+#ifndef CBREAK
+                               vraw();
+#endif
+                       ONERR
+#ifndef CBREAK
+                               vraw();
+#endif
+                               copy(esave, vtube[WECHO], TUBECOLS);
+                       ENDCATCH
+                       fixol();
+                       Pline = OPline;
+                       Putchar = OPutchar;
+                       ungetchar(d);
+                       globp = oglobp;
+
+                       /*
+                        * If we ended up with no lines in the buffer, make
+                        * a line, and don't consider the buffer changed.
+                        */
+                       if (dot == zero) {
+                               fixzero();
+                               sync();
+                       }
+                       splitw = 0;
+
+                       /*
+                        * Special case: did list/number options change?
+                        */
+                       if (onumber != value(NUMBER))
+                               setnumb(value(NUMBER));
+                       if (olist != value(LIST))
+                               setlist(value(LIST));
+
+fixup:
+                       /*
+                        * If a change occurred, other than
+                        * a write which clears changes, then
+                        * we should allow an undo even if .
+                        * didn't move.
+                        *
+                        * BUG: You can make this wrong by
+                        * tricking around with multiple commands
+                        * on one line of : escape, and including
+                        * a write command there, but its not
+                        * worth worrying about.
+                        */
+                       if (tchng && tchng != i)
+                               vundkind = VMANY, cursor = 0;
+
+                       /*
+                        * If we are about to do another :, hold off
+                        * updating of screen.
+                        */
+                       if (vcnt < 0 && Peekkey == ':') {
+                               getDOT();
+                               continue;
+                       }
+
+                       /*
+                        * In the case where the file being edited is
+                        * new; e.g. if the initial state hasn't been
+                        * saved yet, then do so now.
+                        */
+                       if (unddol == truedol) {
+                               vundkind = VNONE;
+                               Vlines = lineDOL();
+                               if (!inglobal)
+                                       savevis();
+                               addr = zero;
+                               vcnt = 0;
+                               if (esave[0] == 0)
+                                       copy(esave, vtube[WECHO], TUBECOLS);
+                       }
+
+                       /*
+                        * If the current line moved reset the cursor position.
+                        */
+                       if (dot != addr) {
+                               vmoving = 0;
+                               cursor = 0;
+                       }
+
+                       /*
+                        * If current line is not on screen or if we are
+                        * in open mode and . moved, then redraw.
+                        */
+                       i = vcline + (dot - addr);
+                       if (i < 0 || i >= vcnt && i >= -vcnt || state != VISUAL && dot != addr) {
+                               if (state == CRTOPEN)
+                                       vup1();
+                               if (vcnt > 0)
+                                       vcnt = 0;
+                               vjumpto(dot, (char *) 0, '.');
+                       } else {
+                               /*
+                                * Current line IS on screen.
+                                * If we did a [Hit return...] then
+                                * restore vcnt and clear screen if in visual
+                                */
+                               vcline = i;
+                               if (vcnt < 0) {
+                                       vcnt = -vcnt;
+                                       if (state == VISUAL)
+                                               vclear();
+                                       else if (state == CRTOPEN)
+                                               vcnt = 0;
+                               }
+
+                               /*
+                                * Limit max value of vcnt based on $
+                                */
+                               i = vcline + lineDOL() - lineDOT() + 1;
+                               if (i < vcnt)
+                                       vcnt = i;
+                               
+                               /*
+                                * Dirty and repaint.
+                                */
+                               vdirty(0, LINES);
+                               vrepaint(cursor);
+                       }
+
+                       /*
+                        * If in visual, put back the echo area
+                        * if it was clobberred.
+                        */
+                       if (state == VISUAL) {
+                               int sdc = destcol, sdl = destline;
+
+                               splitw++;
+                               vigoto(WECHO, 0);
+                               for (i = 0; i < TUBECOLS - 1; i++) {
+                                       if (esave[i] == 0)
+                                               break;
+                                       vputchar(esave[i]);
+                               }
+                               splitw = 0;
+                               vgoto(sdl, sdc);
+                       }
+                       continue;
+
+               /*
+                * u            undo the last changing command.
+                */
+               case 'u':
+                       vundo();
+                       continue;
+
+               /*
+                * U            restore current line to initial state.
+                */
+               case 'U':
+                       vUndo();
+                       continue;
+
+fonfon:
+                       beep();
+                       vmacp = 0;
+                       inopen = 1;     /* might have been -1 */
+                       continue;
+               }
+
+               /*
+                * Rest of commands are decoded by the operate
+                * routine.
+                */
+               operate(c, cnt);
+       }
+}
+
+/*
+ * Grab the word after the cursor so we can look for it as a tag.
+ */
+grabtag()
+{
+       register char *cp, *dp;
+
+       cp = vpastwh(cursor);
+       if (*cp) {
+               dp = lasttag;
+               do {
+                       if (dp < &lasttag[sizeof lasttag - 2])
+                               *dp++ = *cp;
+                       cp++;
+               } while (isalpha(*cp) || isdigit(*cp) || *cp == '_');
+               *dp++ = 0;
+       }
+}
+
+/*
+ * Before appending lines, set up addr1 and
+ * the command mode undo information.
+ */
+prepapp()
+{
+
+       addr1 = dot;
+       deletenone();
+       addr1++;
+       appendnone();
+}
+
+/*
+ * Execute function f with the address bounds addr1
+ * and addr2 surrounding cnt lines starting at dot.
+ */
+vremote(cnt, f, arg)
+       int cnt, (*f)(), arg;
+{
+       register int oing = inglobal;
+
+       addr1 = dot;
+       addr2 = dot + cnt - 1;
+       if (inopen > 0)
+               undap1 = undap2 = dot;
+       inglobal = 0;
+       (*f)(arg);
+       inglobal = oing;
+       if (inopen > 0)
+               vundkind = VMANY;
+       vmcurs = 0;
+}
+
+/*
+ * Save the current contents of linebuf, if it has changed.
+ */
+vsave()
+{
+       char temp[LBSIZE];
+
+       CP(temp, linebuf);
+       if (vundkind == VCHNG || vundkind == VCAPU) {
+               /*
+                * If the undo state is saved in the temporary buffer
+                * vutmp, then we sync this into the temp file so that
+                * we will be able to undo even after we have moved off
+                * the line.  It would be possible to associate a line
+                * with vutmp but we assume that vutmp is only associated
+                * with line dot (e.g. in case ':') above, so beware.
+                */
+               prepapp();
+               strcLIN(vutmp);
+               putmark(dot);
+               vremote(1, yank, 0);
+               vundkind = VMCHNG;
+               notecnt = 0;
+               undkind = UNDCHANGE;
+       }
+       /*
+        * Get the line out of the temp file and do nothing if it hasn't
+        * changed.  This may seem like a loss, but the line will
+        * almost always be in a read buffer so this may well avoid disk i/o.
+        */
+       getDOT();
+       if (strcmp(linebuf, temp) == 0)
+               return;
+       strcLIN(temp);
+       putmark(dot);
+}
+
+#undef forbid
+#define        forbid(a)       if (a) { beep(); return; }
+
+/*
+ * Do a z operation.
+ * Code here is rather long, and very uninteresting.
+ */
+vzop(hadcnt, cnt, c)
+       bool hadcnt;
+       int cnt;
+       register int c;
+{
+       register line *addr;
+
+       if (state != VISUAL) {
+               /*
+                * Z from open; always like a z=.
+                * This code is a mess and should be cleaned up.
+                */
+               vmoveitup(1, 1);
+               vgoto(outline, 0);
+               ostop(normf);
+               setoutt();
+               addr2 = dot;
+               vclear();
+               destline = WECHO;
+               zop2(Xhadcnt ? Xcnt : value(WINDOW) - 1, '=');
+               if (state == CRTOPEN)
+                       putnl();
+               putNFL();
+               termreset();
+               Outchar = vputchar;
+               ignore(ostart());
+               vcnt = 0;
+               outline = destline = 0;
+               vjumpto(dot, cursor, 0);
+               return;
+       }
+       if (hadcnt) {
+               addr = zero + cnt;
+               if (addr < one)
+                       addr = one;
+               if (addr > dol)
+                       addr = dol;
+               markit(addr);
+       } else
+               switch (c) {
+
+               case '+':
+                       addr = dot + vcnt - vcline;
+                       break;
+
+               case '^':
+                       addr = dot - vcline - 1;
+                       forbid (addr < one);
+                       c = '-';
+                       break;
+
+               default:
+                       addr = dot;
+                       break;
+               }
+       switch (c) {
+
+       case '.':
+       case '-':
+               break;
+
+       case '^':
+               forbid (addr <= one);
+               break;
+
+       case '+':
+               forbid (addr >= dol);
+               /* fall into ... */
+
+       case CR:
+       case NL:
+               c = CR;
+               break;
+
+       default:
+               beep();
+               return;
+       }
+       vmoving = 0;
+       vjumpto(addr, NOSTR, c);
+}
diff --git a/usr/src/cmd/ex/ex_voperate.c b/usr/src/cmd/ex/ex_voperate.c
new file mode 100644 (file)
index 0000000..f65d7b2
--- /dev/null
@@ -0,0 +1,828 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+#define        blank()         isspace(wcursor[0])
+#define        forbid(a)       if (a) goto errlab;
+
+char   vscandir[2] =   { '/', 0 };
+
+/*
+ * Decode an operator/operand type command.
+ * Eventually we switch to an operator subroutine in ex_vops.c.
+ * The work here is setting up a function variable to point
+ * to the routine we want, and manipulation of the variables
+ * wcursor and wdot, which mark the other end of the affected
+ * area.  If wdot is zero, then the current line is the other end,
+ * and if wcursor is zero, then the first non-blank location of the
+ * other line is implied.
+ */
+operate(c, cnt)
+       register int c, cnt;
+{
+       register int i;
+       int (*moveop)(), (*deleteop)();
+       register int (*opf)();
+       bool subop = 0;
+       char *oglobp, *ocurs;
+       register line *addr;
+       static char lastFKND, lastFCHR;
+       char d;
+
+       moveop = vmove, deleteop = vdelete;
+       wcursor = cursor;
+       wdot = NOLINE;
+       notecnt = 0;
+       dir = 1;
+       switch (c) {
+
+       /*
+        * d            delete operator.
+        */
+       case 'd':
+               moveop = vdelete;
+               deleteop = beep;
+               break;
+
+       /*
+        * s            substitute characters, like c\040, i.e. change space.
+        */
+       case 's':
+               ungetkey(' ');
+               subop++;
+               /* fall into ... */
+
+       /*
+        * c            Change operator.
+        */
+       case 'c':
+               if (c == 'c' && workcmd[0] == 'C' || workcmd[0] == 'S')
+                       subop++;
+               moveop = vchange;
+               deleteop = beep;
+               break;
+
+       /*
+        * !            Filter through a UNIX command.
+        */
+       case '!':
+               moveop = vfilter;
+               deleteop = beep;
+               break;
+
+       /*
+        * y            Yank operator.  Place specified text so that it
+        *              can be put back with p/P.  Also yanks to named buffers.
+        */
+       case 'y':
+               moveop = vyankit;
+               deleteop = beep;
+               break;
+
+       /*
+        * =            Reformat operator (for LISP).
+        */
+#ifdef LISPCODE
+       case '=':
+               forbid(!value(LISP));
+               /* fall into ... */
+#endif
+
+       /*
+        * >            Right shift operator.
+        * <            Left shift operator.
+        */
+       case '<':
+       case '>':
+               moveop = vshftop;
+               deleteop = beep;
+               break;
+
+       /*
+        * r            Replace character under cursor with single following
+        *              character.
+        */
+       case 'r':
+               vrep(cnt);
+               return;
+
+       default:
+               goto nocount;
+       }
+       /*
+        * Had an operator, so accept another count.
+        * Multiply counts together.
+        */
+       if (isdigit(peekkey()) && peekkey() != '0') {
+               cnt *= vgetcnt();
+               Xcnt = cnt;
+               forbid (cnt <= 0);
+       }
+
+       /*
+        * Get next character, mapping it and saving as
+        * part of command for repeat.
+        */
+       c = map(getesc(),arrows);
+       if (c == 0)
+               return;
+       if (!subop)
+               *lastcp++ = c;
+nocount:
+       opf = moveop;
+       switch (c) {
+
+       /*
+        * b            Back up a word.
+        * B            Back up a word, liberal definition.
+        */
+       case 'b':
+       case 'B':
+               dir = -1;
+               /* fall into ... */
+
+       /*
+        * w            Forward a word.
+        * W            Forward a word, liberal definition.
+        */
+       case 'W':
+       case 'w':
+               wdkind = c & ' ';
+               forbid(lfind(2, cnt, opf, 0) < 0);
+               vmoving = 0;
+               break;
+
+       /*
+        * E            to end of following blank/nonblank word
+        */
+       case 'E':
+               wdkind = 0;
+               goto ein;
+
+       /*
+        * e            To end of following word.
+        */
+       case 'e':
+               wdkind = 1;
+ein:
+               forbid(lfind(3, cnt - 1, opf, 0) < 0);
+               vmoving = 0;
+               break;
+
+       /*
+        * (            Back an s-expression.
+        */
+       case '(':
+               dir = -1;
+               /* fall into... */
+
+       /*
+        * )            Forward an s-expression.
+        */
+       case ')':
+               forbid(lfind(0, cnt, opf, (line *) 0) < 0);
+               markDOT();
+               break;
+
+       /*
+        * {            Back an s-expression, but don't stop on atoms.
+        *              In text mode, a paragraph.  For C, a balanced set
+        *              of {}'s.
+        */
+       case '{':
+               dir = -1;
+               /* fall into... */
+
+       /*
+        * }            Forward an s-expression, but don't stop on atoms.
+        *              In text mode, back paragraph.  For C, back a balanced
+        *              set of {}'s.
+        */
+       case '}':
+               forbid(lfind(1, cnt, opf, (line *) 0) < 0);
+               markDOT();
+               break;
+
+       /*
+        * %            To matching () or {}.  If not at ( or { scan for
+        *              first such after cursor on this line.
+        */
+       case '%':
+               vsave();
+               i = lmatchp((line *) 0);
+               getDOT();
+               forbid(!i);
+               if (opf != vmove)
+                       if (dir > 0)
+                               wcursor++;
+                       else
+                               cursor++;
+               else
+                       markDOT();
+               vmoving = 0;
+               break;
+
+       /*
+        * [            Back to beginning of defun, i.e. an ( in column 1.
+        *              For text, back to a section macro.
+        *              For C, back to a { in column 1 (~~ beg of function.)
+        */
+       case '[':
+               dir = -1;
+               /* fall into ... */
+
+       /*
+        * ]            Forward to next defun, i.e. a ( in column 1.
+        *              For text, forward section.
+        *              For C, forward to a } in column 1 (if delete or such)
+        *              or if a move to a { in column 1.
+        */
+       case ']':
+               if (!vglobp)
+                       forbid(getkey() != c);
+               if (Xhadcnt)
+                       vsetsiz(Xcnt);
+               vsave();
+               i = lbrack(c, opf);
+               getDOT();
+               forbid(!i);
+               markDOT();
+               if (ospeed > B300)
+                       hold |= HOLDWIG;
+               break;
+
+       /*
+        * ,            Invert last find with f F t or T, like inverse
+        *              of ;.
+        */
+       case ',':
+               forbid (lastFKND == 0);
+               c = isupper(lastFKND) ? tolower(lastFKND) : toupper(lastFKND);
+               ungetkey(lastFCHR);
+               if (vglobp == 0)
+                       vglobp = "";
+               subop++;
+               goto nocount;
+
+       /*
+        * 0            To beginning of real line.
+        */
+       case '0':
+               wcursor = linebuf;
+               vmoving = 0;
+               break;
+
+       /*
+        * ;            Repeat last find with f F t or T.
+        */
+       case ';':
+               forbid (lastFKND == 0);
+               c = lastFKND;
+               ungetkey(lastFCHR);
+               subop++;
+               goto nocount;
+
+       /*
+        * F            Find single character before cursor in current line.
+        * T            Like F, but stops before character.
+        */
+       case 'F':       /* inverted find */
+       case 'T':
+               dir = -1;
+               /* fall into ... */
+
+       /*
+        * f            Find single character following cursor in current line.
+        * t            Like f, but stope before character.
+        */
+       case 'f':       /* find */
+       case 't':
+               i = getesc();
+               if (i == 0)
+                       return;
+               if (!subop)
+                       *lastcp++ = i;
+               if (vglobp == 0)
+                       lastFKND = c, lastFCHR = i;
+               for (; cnt > 0; cnt--)
+                       forbid (find(i) == 0);
+               vmoving = 0;
+               switch (c) {
+
+               case 'T':
+                       wcursor++;
+                       break;
+
+               case 't':
+                       wcursor--;
+               case 'f':
+fixup:
+                       if (moveop != vmove)
+                               wcursor++;
+                       break;
+               }
+               break;
+
+       /*
+        * |            Find specified print column in current line.
+        */
+       case '|':
+               if (Pline == numbline)
+                       cnt += 8;
+               vmovcol = cnt;
+               vmoving = 1;
+               wcursor = vfindcol(cnt);
+               break;
+
+       /*
+        * ^            To beginning of non-white space on line.
+        */
+       case '^':
+               wcursor = vskipwh(linebuf);
+               vmoving = 0;
+               break;
+
+       /*
+        * $            To end of line.
+        */
+       case '$':
+               if (opf == vmove) {
+                       vmoving = 1;
+                       vmovcol = 20000;
+               } else
+                       vmoving = 0;
+               if (cnt > 1) {
+                       if (opf == vmove) {
+                               wcursor = 0;
+                               cnt--;
+                       } else
+                               wcursor = linebuf;
+                       /* This is wrong at EOF */
+                       wdot = dot + cnt;
+                       break;
+               }
+               if (linebuf[0]) {
+                       wcursor = strend(linebuf) - 1;
+                       goto fixup;
+               }
+               wcursor = linebuf;
+               break;
+
+       /*
+        * h            Back a character.
+        * ^H           Back a character.
+        */
+       case 'h':
+       case CTRL(h):
+               dir = -1;
+               /* fall into ... */
+
+       /*
+        * space        Forward a character.
+        */
+       case 'l':
+       case ' ':
+               forbid (margin() || opf == vmove && edge());
+               while (cnt > 0 && !margin())
+                       wcursor += dir, cnt--;
+               if (margin() && opf == vmove || wcursor < linebuf)
+                       wcursor -= dir;
+               vmoving = 0;
+               break;
+
+       /*
+        * D            Delete to end of line, short for d$.
+        */
+       case 'D':
+               cnt = INF;
+               goto deleteit;
+
+       /*
+        * X            Delete character before cursor.
+        */
+       case 'X':
+               dir = -1;
+               /* fall into ... */
+deleteit:
+       /*
+        * x            Delete character at cursor, leaving cursor where it is.
+        */
+       case 'x':
+               if (margin())
+                       goto errlab;
+               while (cnt > 0 && !margin())
+                       wcursor += dir, cnt--;
+               opf = deleteop;
+               vmoving = 0;
+               break;
+
+       default:
+               /*
+                * Stuttered operators are equivalent to the operator on
+                * a line, thus turn dd into d_.
+                */
+               if (opf == vmove || c != workcmd[0]) {
+errlab:
+                       beep();
+                       vmacp = 0;
+                       return;
+               }
+               /* fall into ... */
+
+       /*
+        * _            Target for a line or group of lines.
+        *              Stuttering is more convenient; this is mostly
+        *              for aesthetics.
+        */
+       case '_':
+               wdot = dot + cnt - 1;
+               vmoving = 0;
+               wcursor = 0;
+               break;
+
+       /*
+        * H            To first, home line on screen.
+        *              Count is for count'th line rather than first.
+        */
+       case 'H':
+               wdot = (dot - vcline) + cnt - 1;
+               if (opf == vmove)
+                       markit(wdot);
+               vmoving = 0;
+               wcursor = 0;
+               break;
+
+       /*
+        * -            Backwards lines, to first non-white character.
+        */
+       case '-':
+               wdot = dot - cnt;
+               vmoving = 0;
+               wcursor = 0;
+               break;
+
+       /*
+        * ^P           To previous line same column.  Ridiculous on the
+        *              console of the VAX since it puts console in LSI mode.
+        */
+       case 'k':
+       case CTRL(p):
+               wdot = dot - cnt;
+               if (vmoving == 0)
+                       vmoving = 1, vmovcol = column(cursor);
+               wcursor = 0;
+               break;
+
+       /*
+        * L            To last line on screen, or count'th line from the
+        *              bottom.
+        */
+       case 'L':
+               wdot = dot + vcnt - vcline - cnt;
+               if (opf == vmove)
+                       markit(wdot);
+               vmoving = 0;
+               wcursor = 0;
+               break;
+
+       /*
+        * M            To the middle of the screen.
+        */
+       case 'M':
+               wdot = dot + ((vcnt + 1) / 2) - vcline - 1;
+               if (opf == vmove)
+                       markit(wdot);
+               vmoving = 0;
+               wcursor = 0;
+               break;
+
+       /*
+        * +            Forward line, to first non-white.
+        *
+        * CR           Convenient synonym for +.
+        */
+       case '+':
+       case CR:
+               wdot = dot + cnt;
+               vmoving = 0;
+               wcursor = 0;
+               break;
+
+       /*
+        * ^N           To next line, same column if possible.
+        *
+        * LF           Linefeed is a convenient synonym for ^N.
+        */
+       case CTRL(n):
+       case 'j':
+       case NL:
+               wdot = dot + cnt;
+               if (vmoving == 0)
+                       vmoving = 1, vmovcol = column(cursor);
+               wcursor = 0;
+               break;
+
+       /*
+        * n            Search to next match of current pattern.
+        */
+       case 'n':
+               vglobp = vscandir;
+               c = *vglobp++;
+               goto nocount;
+
+       /*
+        * N            Like n but in reverse direction.
+        */
+       case 'N':
+               vglobp = vscandir[0] == '/' ? "?" : "/";
+               c = *vglobp++;
+               goto nocount;
+
+       /*
+        * '            Return to line specified by following mark,
+        *              first white position on line.
+        *
+        * `            Return to marked line at remembered column.
+        */
+       case '\'':
+       case '`':
+               d = c;
+               c = getesc();
+               if (c == 0)
+                       return;
+               c = markreg(c);
+               forbid (c == 0);
+               wdot = getmark(c);
+               forbid (wdot == NOLINE);
+               if (Xhadcnt)
+                       vsetsiz(Xcnt);
+               vmoving = 0;
+               wcursor = d == '`' ? ncols[c - 'a'] : 0;
+               if (opf == vmove && (wdot != dot || (d == '`' && wcursor != cursor)))
+                       markDOT();
+               if (wcursor) {
+                       vsave();
+                       getline(*wdot);
+                       if (wcursor > strend(linebuf))
+                               wcursor = 0;
+                       getDOT();
+               }
+               if (ospeed > B300)
+                       hold |= HOLDWIG;
+               break;
+
+       /*
+        * G            Goto count'th line, or last line if no count
+        *              given.
+        */
+       case 'G':
+               if (!Xhadcnt)
+                       cnt = lineDOL();
+               wdot = zero + cnt;
+               forbid (wdot < one || wdot > dol);
+               if (opf == vmove)
+                       markit(wdot);
+               vmoving = 0;
+               wcursor = 0;
+               break;
+
+       /*
+        * /            Scan forward for following re.
+        * ?            Scan backward for following re.
+        */
+       case '/':
+       case '?':
+               if (Xhadcnt)
+                       vsetsiz(Xcnt);
+               vsave();
+               ocurs = cursor;
+               wcursor = 0;
+               if (readecho(c))
+                       return;
+               if (!vglobp)
+                       vscandir[0] = genbuf[0];
+               oglobp = globp; CP(vutmp, genbuf); globp = vutmp;
+               d = peekc; ungetchar(0); fixech();
+               CATCH
+#ifndef CBREAK
+                       /*
+                        * Lose typeahead (ick).
+                        */
+                       vcook();
+#endif
+                       addr = address(cursor);
+#ifndef CBREAK
+                       vraw();
+#endif
+               ONERR
+#ifndef CBREAK
+                       vraw();
+#endif
+                       globp = oglobp;
+                       ungetchar(d);
+                       splitw = 0;
+                       vclean();
+                       vjumpto(dot, ocurs, 0);
+                       return;
+               ENDCATCH
+               if (globp == 0)
+                       globp = "";
+               else if (peekc)
+                       --globp;
+               ungetchar(d);
+               c = 0;
+               if (*globp == 'z')
+                       globp++, c = '\n';
+               if (any(*globp, "^+-."))
+                       c = *globp++;
+               i = 0;
+               while (isdigit(*globp))
+                       i = i * 10 + *globp++ - '0';
+               if (*globp)
+                       c = *globp++;
+               globp = oglobp;
+               splitw = 0;
+               vmoving = 0;
+               wcursor = loc1;
+               if (i != 0)
+                       vsetsiz(i);
+               if (opf == vmove) {
+                       if (state == ONEOPEN || state == HARDOPEN)
+                               outline = destline = WBOT;
+                       if (addr != dot || loc1 != cursor)
+                               markDOT();
+                       if (loc1 > linebuf && *loc1 == 0)
+                               loc1--;
+                       if (c)
+                               vjumpto(addr, loc1, c);
+                       else {
+                               vmoving = 0;
+                               if (loc1) {
+                                       vmoving++;
+                                       vmovcol = column(loc1);
+                               }
+                               getDOT();
+                               if (state == CRTOPEN && addr != dot)
+                                       vup1();
+                               vupdown(addr - dot, NOSTR);
+                       }
+                       return;
+               }
+               lastcp[-1] = 'n';
+               getDOT();
+               wdot = addr;
+               break;
+       }
+       /*
+        * Apply.
+        */
+       if (vreg && wdot == 0)
+               wdot = dot;
+       (*opf)(c);
+       wdot = NOLINE;
+}
+
+/*
+ * Find single character c, in direction dir from cursor.
+ */
+find(c)
+       char c;
+{
+
+       for(;;) {
+               if (edge())
+                       return (0);
+               wcursor += dir;
+               if (*wcursor == c)
+                       return (1);
+       }
+}
+
+/*
+ * Do a word motion with operator op, and cnt more words
+ * to go after this.
+ */
+word(op, cnt)
+       register int (*op)();
+       int cnt;
+{
+       register int which;
+       register char *iwc;
+       register line *iwdot = wdot;
+
+       if (dir == 1) {
+               iwc = wcursor;
+               which = wordch(wcursor);
+               while (wordof(which, wcursor)) {
+                       if (cnt == 1 && op != vmove && wcursor[1] == 0) {
+                               wcursor++;
+                               break;
+                       }
+                       if (!lnext())
+                               return (0);
+                       if (wcursor == linebuf)
+                               break;
+               }
+               /* Unless last segment of a change skip blanks */
+               if (op != vchange || cnt > 1)
+                       while (!margin() && blank())
+                               wcursor++;
+               else
+                       if (wcursor == iwc && iwdot == wdot && *iwc)
+                               wcursor++;
+               if (op == vmove && margin())
+                       wcursor--;
+       } else {
+               if (!lnext())
+                       return (0);
+               while (blank())
+                       if (!lnext())
+                               return (0);
+               if (!margin()) {
+                       which = wordch(wcursor);
+                       while (!margin() && wordof(which, wcursor))
+                               wcursor--;
+               }
+               if (wcursor < linebuf || !wordof(which, wcursor))
+                       wcursor++;
+       }
+       return (1);
+}
+
+/*
+ * To end of word, with operator op and cnt more motions
+ * remaining after this.
+ */
+eend(op)
+       register int (*op)();
+{
+       register int which;
+
+       if (!lnext())
+               return;
+       while (blank())
+               if (!lnext())
+                       return;
+       which = wordch(wcursor);
+       while (wordof(which, wcursor)) {
+               if (wcursor[1] == 0) {
+                       wcursor++;
+                       break;
+               }
+               if (!lnext())
+                       return;
+       }
+       if (op != vchange && op != vdelete && wcursor > linebuf)
+               wcursor--;
+}
+
+/*
+ * Wordof tells whether the character at *wc is in a word of
+ * kind which (blank/nonblank words are 0, conservative words 1).
+ */
+wordof(which, wc)
+       char which;
+       register char *wc;
+{
+
+       if (isspace(*wc))
+               return (0);
+       return (!wdkind || wordch(wc) == which);
+}
+
+/*
+ * Wordch tells whether character at *wc is a word character
+ * i.e. an alfa, digit, or underscore.
+ */
+wordch(wc)
+       char *wc;
+{
+       register int c;
+
+       c = wc[0];
+       return (isalpha(c) || isdigit(c) || c == '_');
+}
+
+/*
+ * Edge tells when we hit the last character in the current line.
+ */
+edge()
+{
+
+       if (linebuf[0] == 0)
+               return (1);
+       if (dir == 1)
+               return (wcursor[1] == 0);
+       else
+               return (wcursor == linebuf);
+}
+
+/*
+ * Margin tells us when we have fallen off the end of the line.
+ */
+margin()
+{
+
+       return (wcursor < linebuf || wcursor[0] == 0);
+}
diff --git a/usr/src/cmd/ex/ex_vops.c b/usr/src/cmd/ex/ex_vops.c
new file mode 100644 (file)
index 0000000..92a5f68
--- /dev/null
@@ -0,0 +1,812 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * This file defines the operation sequences which interface the
+ * logical changes to the file buffer with the internal and external
+ * display representations.
+ */
+
+/*
+ * Undo.
+ *
+ * Undo is accomplished in two ways.  We often for small changes in the
+ * current line know how (in terms of a change operator) how the change
+ * occurred.  Thus on an intelligent terminal we can undo the operation
+ * by another such operation, using insert and delete character
+ * stuff.  The pointers vU[AD][12] index the buffer vutmp when this
+ * is possible and provide the necessary information.
+ *
+ * The other case is that the change involved multiple lines or that
+ * we have moved away from the line or forgotten how the change was
+ * accomplished.  In this case we do a redisplay and hope that the
+ * low level optimization routines (which don't look for winning
+ * via insert/delete character) will not lose too badly.
+ */
+char   *vUA1, *vUA2;
+char   *vUD1, *vUD2;
+
+vUndo()
+{
+
+       /*
+        * Avoid UU which clobbers ability to do u.
+        */
+       if (vundkind == VCAPU || vUNDdot != dot) {
+               beep();
+               return;
+       }
+       CP(vutmp, linebuf);
+       vUD1 = linebuf; vUD2 = strend(linebuf);
+       putmk1(dot, vUNDsav);
+       getDOT();
+       vUA1 = linebuf; vUA2 = strend(linebuf);
+       vundkind = VCAPU;
+       if (state == ONEOPEN || state == HARDOPEN) {
+               vjumpto(dot, vUNDcurs, 0);
+               return;
+       }
+       vdirty(vcline, 1);
+       vsyncCL();
+       vfixcurs();
+}
+
+vundo()
+{
+       register int cnt;
+       register line *addr;
+       register char *cp;
+       char temp[LBSIZE];
+       bool savenote;
+       int (*OO)();
+       short oldhold = hold;
+
+       switch (vundkind) {
+
+       case VMANYINS:
+               wcursor = 0;
+               addr1 = undap1;
+               addr2 = undap2 - 1;
+               vsave();
+               YANKreg('1');
+               notecnt = 0;
+               /* fall into ... */
+
+       case VMANY:
+       case VMCHNG:
+               vsave();
+               addr = dot - vcline;
+               notecnt = 1;
+               if (undkind == UNDPUT && undap1 == undap2) {
+                       beep();
+                       return;
+               }
+               /*
+                * Undo() call below basically replaces undap1 to undap2-1
+                * with dol through unddol-1.  Hack screen image to
+                * reflect this replacement.
+                */
+               vreplace(undap1 - addr, undap2 - undap1,
+                   undkind == UNDPUT ? 0 : unddol - dol);
+               savenote = notecnt;
+               undo(1);
+               if (vundkind != VMCHNG || addr != dot)
+                       killU();
+               vundkind = VMANY;
+               cnt = dot - addr;
+               if (cnt < 0 || cnt > vcnt || state != VISUAL) {
+                       vjumpto(dot, NOSTR, '.');
+                       return;
+               }
+               if (!savenote)
+                       notecnt = 0;
+               vcline = cnt;
+               vrepaint(vmcurs);
+               vmcurs = 0;
+               return;
+
+       case VCHNG:
+       case VCAPU:
+               vundkind = VCHNG;
+               strcpy(temp, vutmp);
+               strcpy(vutmp, linebuf);
+               doomed = column(vUA2 - 1) - column(vUA1 - 1);
+               strcLIN(temp);
+               cp = vUA1; vUA1 = vUD1; vUD1 = cp;
+               cp = vUA2; vUA2 = vUD2; vUD2 = cp;
+               cursor = vUD1;
+               if (state == HARDOPEN) {
+                       doomed = 0;
+                       vsave();
+                       vopen(dot, WBOT);
+                       vnline(cursor);
+                       return;
+               }
+               /*
+                * Pseudo insert command.
+                */
+               vcursat(cursor);
+               OO = Outchar; Outchar = vinschar; hold |= HOLDQIK;
+               vprepins();
+               temp[vUA2 - linebuf] = 0;
+               for (cp = &temp[vUA1 - linebuf]; *cp;)
+                       putchar(*cp++);
+               Outchar = OO; hold = oldhold;
+               endim();
+               physdc(cindent(), cindent() + doomed);
+               doomed = 0;
+               vdirty(vcline, 1);
+               vsyncCL();
+               if (cursor > linebuf && cursor >= strend(linebuf))
+                       cursor--;
+               vfixcurs();
+               return;
+
+       case VNONE:
+               beep();
+               return;
+       }
+}
+
+/*
+ * Initialize undo information before an append.
+ */
+vnoapp()
+{
+
+       vUD1 = vUD2 = cursor;
+}
+
+/*
+ * All the rest of the motion sequences have one or more
+ * cases to deal with.  In the case wdot == 0, operation
+ * is totally within current line, from cursor to wcursor.
+ * If wdot is given, but wcursor is 0, then operation affects
+ * the inclusive line range.  The hardest case is when both wdot
+ * and wcursor are given, then operation affects from line dot at
+ * cursor to line wdot at wcursor.
+ */
+
+/*
+ * Move is simple, except for moving onto new lines in hardcopy open mode.
+ */
+vmove()
+{
+       register int cnt;
+
+       if (wdot) {
+               if (wdot < one || wdot > dol) {
+                       beep();
+                       return;
+               }
+               cnt = wdot - dot;
+               wdot = NOLINE;
+               if (cnt)
+                       killU();
+               vupdown(cnt, wcursor);
+               return;
+       }
+
+       /*
+        * When we move onto a new line, save information for U undo.
+        */
+       if (vUNDdot != dot) {
+               vUNDsav = *dot;
+               vUNDcurs = wcursor;
+               vUNDdot = dot;
+       }
+
+       /*
+        * In hardcopy open, type characters to left of cursor
+        * on new line, or back cursor up if its to left of where we are.
+        * In any case if the current line is ``rubbled'' i.e. has trashy
+        * looking overstrikes on it or \'s from deletes, we reprint
+        * so it is more comprehensible (and also because we can't work
+        * if we let it get more out of sync since column() won't work right.
+        */
+       if (state == HARDOPEN) {
+               register char *cp;
+               if (rubble) {
+                       register int c;
+                       int oldhold = hold;
+
+                       sethard();
+                       cp = wcursor;
+                       c = *cp;
+                       *cp = 0;
+                       hold |= HOLDDOL;
+                       vreopen(WTOP, lineDOT(), vcline);
+                       hold = oldhold;
+                       *cp = c;
+               } else if (wcursor > cursor) {
+                       vfixcurs();
+                       for (cp = cursor; *cp && cp < wcursor;) {
+                               register int c = *cp++ & TRIM;
+
+                               putchar(c ? c : ' ');
+                       }
+               }
+       }
+       vsetcurs(wcursor);
+}
+
+/*
+ * Delete operator.
+ *
+ * Hard case of deleting a range where both wcursor and wdot
+ * are specified is treated as a special case of change and handled
+ * by vchange (although vchange may pass it back if it degenerates
+ * to a full line range delete.)
+ */
+vdelete(c)
+       char c;
+{
+       register char *cp;
+       register int i;
+
+       if (wdot) {
+               if (wcursor) {
+                       vchange('d');
+                       return;
+               }
+               if ((i = xdw()) < 0)
+                       return;
+               if (state != VISUAL) {
+                       vgoto(LINE(0), 0);
+                       vputchar('@');
+               }
+               wdot = dot;
+               vremote(i, delete, 0);
+               notenam = "delete";
+               DEL[0] = 0;
+               killU();
+               vreplace(vcline, i, 0);
+               if (wdot > dol)
+                       vcline--;
+               vrepaint(NOSTR);
+               return;
+       }
+       if (wcursor < linebuf)
+               wcursor = linebuf;
+       if (cursor == wcursor) {
+               beep();
+               return;
+       }
+       i = vdcMID();
+       cp = cursor;
+       setDEL();
+       CP(cp, wcursor);
+       if (cp > linebuf && (cp[0] == 0 || c == '#'))
+               cp--;
+       if (state == HARDOPEN) {
+               bleep(i, cp);
+               cursor = cp;
+               return;
+       }
+       physdc(column(cursor - 1), i);
+       DEPTH(vcline) = 0;
+       vreopen(LINE(vcline), lineDOT(), vcline);
+       vsyncCL();
+       vsetcurs(cp);
+}
+
+/*
+ * Change operator.
+ *
+ * In a single line we mark the end of the changed area with '$'.
+ * On multiple whole lines, we clear the lines first.
+ * Across lines with both wcursor and wdot given, we delete
+ * and sync then append (but one operation for undo).
+ */
+vchange(c)
+       char c;
+{
+       register char *cp;
+       register int i, ind, cnt;
+       line *addr;
+
+       if (wdot) {
+               /*
+                * Change/delete of lines or across line boundaries.
+                */
+               if ((cnt = xdw()) < 0)
+                       return;
+               getDOT();
+               if (wcursor && cnt == 1) {
+                       /*
+                        * Not really.
+                        */
+                       wdot = 0;
+                       if (c == 'd') {
+                               vdelete(c);
+                               return;
+                       }
+                       goto smallchange;
+               }
+               if (cursor && wcursor) {
+                       /*
+                        * Across line boundaries, but not
+                        * necessarily whole lines.
+                        * Construct what will be left.
+                        */
+                       *cursor = 0;
+                       strcpy(genbuf, linebuf);
+                       getline(*wdot);
+                       if (strlen(genbuf) + strlen(wcursor) > LBSIZE - 2) {
+                               getDOT();
+                               beep();
+                               return;
+                       }
+                       strcat(genbuf, wcursor);
+                       if (c == 'd' && *vpastwh(genbuf) == 0) {
+                               /*
+                                * Although this is a delete
+                                * spanning line boundaries, what
+                                * would be left is all white space,
+                                * so take it all away.
+                                */
+                               wcursor = 0;
+                               getDOT();
+                               op = 0;
+                               notpart(lastreg);
+                               notpart('1');
+                               vdelete(c);
+                               return;
+                       }
+                       ind = -1;
+               } else if (c == 'd' && wcursor == 0) {
+                       vdelete(c);
+                       return;
+               } else
+#ifdef LISPCODE
+                       /*
+                        * We are just substituting text for whole lines,
+                        * so determine the first autoindent.
+                        */
+                       if (value(LISP) && value(AUTOINDENT))
+                               ind = lindent(dot);
+                       else
+#endif
+                               ind = whitecnt(linebuf);
+               i = vcline >= 0 ? LINE(vcline) : WTOP;
+
+               /*
+                * Delete the lines from the buffer,
+                * and remember how the partial stuff came about in
+                * case we are told to put.
+                */
+               addr = dot;
+               vremote(cnt, delete, 0);
+               setpk();
+               notenam = "delete";
+               if (c != 'd')
+                       notenam = "change";
+               /*
+                * If DEL[0] were nonzero, put would put it back
+                * rather than the deleted lines.
+                */
+               DEL[0] = 0;
+               if (cnt > 1)
+                       killU();
+
+               /*
+                * Now hack the screen image coordination.
+                */
+               vreplace(vcline, cnt, 0);
+               wdot = NOLINE;
+               noteit(0);
+               vcline--;
+               if (addr <= dol)
+                       dot--;
+
+               /*
+                * If this is a across line delete/change,
+                * cursor stays where it is; just splice together the pieces
+                * of the new line.  Otherwise generate a autoindent
+                * after a S command.
+                */
+               if (ind >= 0) {
+                       *genindent(ind) = 0;
+                       vdoappend(genbuf);
+               } else {
+                       vmcurs = cursor;
+                       strcLIN(genbuf);
+                       vdoappend(linebuf);
+               }
+
+               /*
+                * Indicate a change on hardcopies by
+                * erasing the current line.
+                */
+               if (c != 'd' && state != VISUAL && state != HARDOPEN) {
+                       int oldhold = hold;
+
+                       hold |= HOLDAT, vclrlin(i, dot), hold = oldhold;
+               }
+
+               /*
+                * Open the line (logically) on the screen, and 
+                * update the screen tail.  Unless we are really a delete
+                * go off and gather up inserted characters.
+                */
+               vcline++;
+               if (vcline < 0)
+                       vcline = 0;
+               vopen(dot, i);
+               vsyncCL();
+               noteit(1);
+               if (c != 'd') {
+                       if (ind >= 0) {
+                               cursor = linebuf;
+                               linebuf[0] = 0;
+                               vfixcurs();
+                       } else {
+                               ind = 0;
+                               vcursat(cursor);
+                       }
+                       vappend('x', 1, ind);
+                       return;
+               }
+               if (*cursor == 0 && cursor > linebuf)
+                       cursor--;
+               vrepaint(cursor);
+               return;
+       }
+
+smallchange:
+       /*
+        * The rest of this is just low level hacking on changes
+        * of small numbers of characters.
+        */
+       if (wcursor < linebuf)
+               wcursor = linebuf;
+       if (cursor == wcursor) {
+               beep();
+               return;
+       }
+       i = vdcMID();
+       cp = cursor;
+       if (state != HARDOPEN)
+               vfixcurs();
+
+       /*
+        * Put out the \\'s indicating changed text in hardcopy,
+        * or mark the end of the change with $ if not hardcopy.
+        */
+       if (state == HARDOPEN) 
+               bleep(i, cp);
+       else {
+               vcursbef(wcursor);
+               putchar('$');
+               i = cindent();
+       }
+
+       /*
+        * Remember the deleted text for possible put,
+        * and then prepare and execute the input portion of the change.
+        */
+       cursor = cp;
+       setDEL();
+       CP(cursor, wcursor);
+       if (state != HARDOPEN) {
+               vcursaft(cursor - 1);
+               doomed = i - cindent();
+       } else {
+/*
+               sethard();
+               wcursor = cursor;
+               cursor = linebuf;
+               vgoto(outline, value(NUMBER) << 3);
+               vmove();
+*/
+               doomed = 0;
+       }
+       prepapp();
+       vappend('c', 1, 0);
+}
+
+/*
+ * Open new lines.
+ *
+ * Tricky thing here is slowopen.  This causes display updating
+ * to be held off so that 300 baud dumb terminals don't lose badly.
+ * This also suppressed counts, which otherwise say how many blank
+ * space to open up.  Counts are also suppressed on intelligent terminals.
+ * Actually counts are obsoleted, since if your terminal is slow
+ * you are better off with slowopen.
+ */
+voOpen(c, cnt)
+       char c;
+       register int cnt;
+{
+       register int ind = 0, i;
+       short oldhold = hold;
+
+       if (value(SLOWOPEN) || value(REDRAW) && AL && DL)
+               cnt = 1;
+       vsave();
+       setLAST();
+       if (value(AUTOINDENT))
+               ind = whitecnt(linebuf);
+       if (c == 'O') {
+               vcline--;
+               dot--;
+               if (dot > zero)
+                       getDOT();
+       }
+       if (value(AUTOINDENT)) {
+#ifdef LISPCODE
+               if (value(LISP))
+                       ind = lindent(dot + 1);
+#endif
+       }
+       killU();
+       prepapp();
+       vundkind = VMANY;
+       if (state != VISUAL)
+               c = WBOT + 1;
+       else {
+               c = vcline < 0 ? WTOP - cnt : LINE(vcline) + DEPTH(vcline);
+               if (c < ZERO)
+                       c = ZERO;
+               i = LINE(vcline + 1) - c;
+               if (i < cnt && c <= WBOT && (!AL || !DL))
+                       vinslin(c, cnt - i, vcline);
+       }
+       *genindent(ind) = 0;
+       vdoappend(genbuf);
+       vcline++;
+       oldhold = hold;
+       hold |= HOLDROL;
+       vopen(dot, c);
+       hold = oldhold;
+       if (value(SLOWOPEN))
+               /*
+                * Oh, so lazy!
+                */
+               vscrap();
+       else
+               vsync1(LINE(vcline));
+       cursor = linebuf;
+       linebuf[0] = 0;
+       vappend('o', 1, ind);
+}
+
+/*
+ * > < and = shift operators.
+ *
+ * Note that =, which aligns lisp, is just a ragged sort of shift,
+ * since it never distributes text between lines.
+ */
+char   vshnam[2] = { 'x', 0 };
+
+vshftop()
+{
+       register line *addr;
+       register int cnt;
+
+       if ((cnt = xdw()) < 0)
+               return;
+       addr = dot;
+       vremote(cnt, vshift, 0);
+       vshnam[0] = op;
+       notenam = vshnam;
+       dot = addr;
+       vreplace(vcline, cnt, cnt);
+       if (state == HARDOPEN)
+               vcnt = 0;
+       vrepaint(NOSTR);
+}
+
+/*
+ * !.
+ *
+ * Filter portions of the buffer through unix commands.
+ */
+vfilter()
+{
+       register line *addr;
+       register int cnt;
+       char *oglobp, d;
+
+       if ((cnt = xdw()) < 0)
+               return;
+       if (vglobp)
+               vglobp = uxb;
+       if (readecho('!'))
+               return;
+       oglobp = globp; globp = genbuf + 1;
+       d = peekc; ungetchar(0);
+       CATCH
+               fixech();
+               unix0(0);
+       ONERR
+               splitw = 0;
+               ungetchar(d);
+               vrepaint(cursor);
+               globp = oglobp;
+               return;
+       ENDCATCH
+       ungetchar(d); globp = oglobp;
+       addr = dot;
+       CATCH
+               vgoto(WECHO, 0); flusho();
+               vremote(cnt, filter, 2);
+       ONERR
+               vdirty(0, LINES);
+       ENDCATCH
+       if (dot == zero && dol > zero)
+               dot = one;
+       splitw = 0;
+       notenam = "";
+       vreplace(vcline, cnt, undap2 - undap1);
+       dot = addr;
+       if (dot > dol) {
+               dot--;
+               vcline--;
+       }
+       vrepaint(NOSTR);
+}
+
+/*
+ * Xdw exchanges dot and wdot if appropriate and also checks
+ * that wdot is reasonable.  Its name comes from
+ *     xchange dotand wdot
+ */
+xdw()
+{
+       register char *cp;
+       register int cnt;
+/*
+       register int notp = 0;
+ */
+
+       if (wdot == NOLINE || wdot < one || wdot > dol) {
+               beep();
+               return (-1);
+       }
+       vsave();
+       setLAST();
+       if (dot > wdot) {
+               register line *addr;
+
+               vcline -= dot - wdot;
+               addr = dot; dot = wdot; wdot = addr;
+               cp = cursor; cursor = wcursor; wcursor = cp;
+       }
+       /*
+        * If a region is specified but wcursor is at the begining
+        * of the last line, then we move it to be the end of the
+        * previous line (actually off the end).
+        */
+       if (cursor && wcursor == linebuf && wdot > dot) {
+               wdot--;
+               getDOT();
+               if (vpastwh(linebuf) >= cursor)
+                       wcursor = 0;
+               else {
+                       getline(*wdot);
+                       wcursor = strend(linebuf);
+                       getDOT();
+               }
+               /*
+                * Should prepare in caller for possible dot == wdot.
+                */
+       }
+       cnt = wdot - dot + 1;
+       if (vreg) {
+               vremote(cnt, YANKreg, vreg);
+/*
+               if (notp)
+                       notpart(vreg);
+ */
+       }
+
+       /*
+        * Kill buffer code.  If delete operator is c or d, then save
+        * the region in numbered buffers.
+        *
+        * BUG:                 This may be somewhat inefficient due
+        *                      to the way named buffer are implemented,
+        *                      necessitating some optimization.
+        */
+       vreg = 0;
+       if (any(op, "cd")) {
+               vremote(cnt, YANKreg, '1');
+/*
+               if (notp)
+                       notpart('1');
+ */
+       }
+       return (cnt);
+}
+
+/*
+ * Routine for vremote to call to implement shifts.
+ */
+vshift()
+{
+
+       shift(op, 1);
+}
+
+/*
+ * Replace a single character with the next input character.
+ * A funny kind of insert.
+ */
+vrep(cnt)
+       register int cnt;
+{
+       register int i, c;
+
+       if (cnt > strlen(cursor)) {
+               beep();
+               return;
+       }
+       i = column(cursor + cnt - 1);
+       vcursat(cursor);
+       doomed = i - cindent();
+       if (!vglobp) {
+               c = getesc();
+               if (c == 0) {
+                       vfixcurs();
+                       return;
+               }
+               ungetkey(c);
+       }
+       CP(vutmp, linebuf);
+       vundkind = VCHNG;
+       wcursor = cursor + cnt;
+       vUD1 = cursor; vUD2 = wcursor;
+       CP(cursor, wcursor);
+       prepapp();
+       vappend('r', cnt, 0);
+       *lastcp++ = INS[0];
+       setLAST();
+}
+
+/*
+ * Yank.
+ *
+ * Yanking to string registers occurs for free (essentially)
+ * in the routine xdw().
+ */
+vyankit()
+{
+       register int cnt;
+
+       if (wdot) {
+               if ((cnt = xdw()) < 0)
+                       return;
+               vremote(cnt, yank, 0);
+               setpk();
+               notenam = "yank";
+               vundkind = VNONE;
+               DEL[0] = 0;
+               wdot = NOLINE;
+               if (notecnt <= vcnt - vcline && notecnt < value(REPORT))
+                       notecnt = 0;
+               vrepaint(cursor);
+               return;
+       }
+       takeout(DEL);
+}
+
+/*
+ * Set pkill variables so a put can
+ * know how to put back partial text.
+ * This is necessary because undo needs the complete
+ * line images to be saved, while a put wants to trim
+ * the first and last lines.  The compromise
+ * is for put to be more clever.
+ */
+setpk()
+{
+
+       if (wcursor) {
+               pkill[0] = cursor;
+               pkill[1] = wcursor;
+       }
+}
diff --git a/usr/src/cmd/ex/ex_vops2.c b/usr/src/cmd/ex/ex_vops2.c
new file mode 100644 (file)
index 0000000..86334bc
--- /dev/null
@@ -0,0 +1,782 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * Low level routines for operations sequences,
+ * and mostly, insert mode (and a subroutine
+ * to read an input line, including in the echo area.)
+ */
+char   *vUA1, *vUA2;
+char   *vUD1, *vUD2;
+
+/*
+ * Obleeperate characters in hardcopy
+ * open with \'s.
+ */
+bleep(i, cp)
+       register int i;
+       char *cp;
+{
+
+       i -= column(cp);
+       do
+               putchar('\\' | QUOTE);
+       while (--i >= 0);
+       rubble = 1;
+}
+
+/*
+ * Common code for middle part of delete
+ * and change operating on parts of lines.
+ */
+vdcMID()
+{
+       register char *cp;
+
+       squish();
+       setLAST();
+       vundkind = VCHNG, CP(vutmp, linebuf);
+       if (wcursor < cursor)
+               cp = wcursor, wcursor = cursor, cursor = cp;
+       vUD1 = vUA1 = vUA2 = cursor; vUD2 = wcursor;
+       return (column(wcursor - 1));
+}
+
+/*
+ * Take text from linebuf and stick it
+ * in the VBSIZE buffer BUF.  Used to save
+ * deleted text of part of line.
+ */
+takeout(BUF)
+       char *BUF;
+{
+       register char *cp;
+
+       if (wcursor < linebuf)
+               wcursor = linebuf;
+       if (cursor == wcursor) {
+               beep();
+               return;
+       }
+       if (wcursor < cursor) {
+               cp = wcursor;
+               wcursor = cursor;
+               cursor = cp;
+       }
+       setBUF(BUF);
+       if ((BUF[0] & (QUOTE|TRIM)) == OVERBUF)
+               beep();
+}
+
+/*
+ * Are we at the end of the printed representation of the
+ * line?  Used internally in hardcopy open.
+ */
+ateopr()
+{
+       register int i, c;
+       register char *cp = vtube[destline] + destcol;
+
+       for (i = WCOLS - destcol; i > 0; i--) {
+               c = *cp++;
+               if (c == 0)
+                       return (1);
+               if (c != ' ' && (c & QUOTE) == 0)
+                       return (0);
+       }
+       return (1);
+}
+
+/*
+ * Append.
+ *
+ * This routine handles the top level append, doing work
+ * as each new line comes in, and arranging repeatability.
+ * It also handles append with repeat counts, and calculation
+ * of autoindents for new lines.
+ */
+bool   vaifirst;
+bool   gobbled;
+char   *ogcursor;
+
+vappend(ch, cnt, indent)
+       char ch;
+       int cnt, indent;
+{
+       register int i;
+       register char *gcursor;
+       bool escape;
+       int repcnt;
+       short oldhold = hold;
+
+       /*
+        * Before a move in hardopen when the line is dirty
+        * or we are in the middle of the printed representation,
+        * we retype the line to the left of the cursor so the
+        * insert looks clean.
+        */
+       if (ch != 'o' && state == HARDOPEN && (rubble || !ateopr())) {
+               rubble = 1;
+               gcursor = cursor;
+               i = *gcursor;
+               *gcursor = ' ';
+               wcursor = gcursor;
+               vmove();
+               *gcursor = i;
+       }
+       vaifirst = indent == 0;
+
+       /*
+        * Handle replace character by (eventually)
+        * limiting the number of input characters allowed
+        * in the vgetline routine.
+        */
+       if (ch == 'r')
+               repcnt = 2;
+       else
+               repcnt = 0;
+
+       /*
+        * If an autoindent is specified, then
+        * generate a mixture of blanks to tabs to implement
+        * it and place the cursor after the indent.
+        * Text read by the vgetline routine will be placed in genbuf,
+        * so the indent is generated there.
+        */
+       if (value(AUTOINDENT) && indent != 0) {
+               gcursor = genindent(indent);
+               *gcursor = 0;
+               vgotoCL(qcolumn(cursor - 1, genbuf));
+       } else {
+               gcursor = genbuf;
+               *gcursor = 0;
+               if (ch == 'o')
+                       vfixcurs();
+       }
+
+       /*
+        * Prepare for undo.  Pointers delimit inserted portion of line.
+        */
+       vUA1 = vUA2 = cursor;
+
+       /*
+        * If we are not in a repeated command and a ^@ comes in
+        * then this means the previous inserted text.
+        * If there is none or it was too long to be saved,
+        * then beep() and also arrange to undo any damage done
+        * so far (e.g. if we are a change.)
+        */
+       if ((vglobp && *vglobp == 0) || peekbr()) {
+               if ((INS[0] & (QUOTE|TRIM)) == OVERBUF) {
+                       beep();
+                       if (!splitw)
+                               ungetkey('u');
+                       doomed = 0;
+                       hold = oldhold;
+                       return;
+               }
+               /*
+                * Unread input from INS.
+                * An escape will be generated at end of string.
+                * Hold off n^^2 type update on dumb terminals.
+                */
+               vglobp = INS;
+               hold |= HOLDQIK;
+       } else if (vglobp == 0)
+               /*
+                * Not a repeated command, get
+                * a new inserted text for repeat.
+                */
+               INS[0] = 0;
+
+       /*
+        * For wrapmargin to hack away second space after a '.'
+        * when the first space caused a line break we keep
+        * track that this happened in gobblebl, which says
+        * to gobble up a blank silently.
+        */
+       gobblebl = 0;
+
+       /*
+        * Text gathering loop.
+        * New text goes into genbuf starting at gcursor.
+        * cursor preserves place in linebuf where text will eventually go.
+        */
+       if (*cursor == 0 || state == CRTOPEN)
+               hold |= HOLDROL;
+       for (;;) {
+               if (ch == 'r' && repcnt == 0)
+                       escape = 0;
+               else {
+                       gcursor = vgetline(repcnt, gcursor, &escape);
+
+                       /*
+                        * After an append, stick information
+                        * about the ^D's and ^^D's and 0^D's in
+                        * the repeated text buffer so repeated
+                        * inserts of stuff indented with ^D as backtab's
+                        * can work.
+                        */
+                       if (HADUP)
+                               addtext("^");
+                       else if (HADZERO)
+                               addtext("0");
+                       while (CDCNT > 0)
+                               addtext("\204"), CDCNT--;
+                       if (gobbled)
+                               addtext(" ");
+                       addtext(ogcursor);
+               }
+               repcnt = 0;
+
+               /*
+                * Smash the generated and preexisting indents together
+                * and generate one cleanly made out of tabs and spaces
+                * if we are using autoindent.
+                */
+               if (!vaifirst && value(AUTOINDENT)) {
+                       i = fixindent(indent);
+                       if (!HADUP)
+                               indent = i;
+                       gcursor = strend(genbuf);
+               }
+
+               /*
+                * Limit the repetition count based on maximum
+                * possible line length; do output implied
+                * by further count (> 1) and cons up the new line
+                * in linebuf.
+                */
+               cnt = vmaxrep(ch, cnt);
+               CP(gcursor + 1, cursor);
+               do {
+                       CP(cursor, genbuf);
+                       if (cnt > 1) {
+                               int oldhold = hold;
+
+                               Outchar = vinschar;
+                               hold |= HOLDQIK;
+                               printf("%s", genbuf);
+                               hold = oldhold;
+                               Outchar = vputchar;
+                       }
+                       cursor += gcursor - genbuf;
+               } while (--cnt > 0);
+               endim();
+               vUA2 = cursor;
+               if (escape != '\n')
+                       CP(cursor, gcursor + 1);
+
+               /*
+                * If doomed characters remain, clobber them,
+                * and reopen the line to get the display exact.
+                */
+               if (state != HARDOPEN) {
+                       DEPTH(vcline) = 0;
+                       if (doomed > 0) {
+                               register int cind = cindent();
+
+                               physdc(cind, cind + doomed);
+                               doomed = 0;
+                       }
+                       i = vreopen(LINE(vcline), lineDOT(), vcline);
+               }
+
+               /*
+                * All done unless we are continuing on to another line.
+                */
+               if (escape != '\n')
+                       break;
+
+               /*
+                * Set up for the new line.
+                * First save the current line, then construct a new
+                * first image for the continuation line consisting
+                * of any new autoindent plus the pushed ahead text.
+                */
+               killU();
+               addtext(gobblebl ? " " : "\n");
+               vsave();
+               cnt = 1;
+               if (value(AUTOINDENT)) {
+#ifdef LISPCODE
+                       if (value(LISP))
+                               indent = lindent(dot + 1);
+                       else
+#endif
+                            if (!HADUP && vaifirst)
+                               indent = whitecnt(linebuf);
+                       vaifirst = 0;
+                       strcLIN(vpastwh(gcursor + 1));
+                       gcursor = genindent(indent);
+                       *gcursor = 0;
+                       if (gcursor + strlen(linebuf) > &genbuf[LBSIZE - 2])
+                               gcursor = genbuf;
+                       CP(gcursor, linebuf);
+               } else {
+                       CP(genbuf, gcursor + 1);
+                       gcursor = genbuf;
+               }
+
+               /*
+                * If we started out as a single line operation and are now
+                * turning into a multi-line change, then we had better yank
+                * out dot before it changes so that undo will work
+                * correctly later.
+                */
+               if (vundkind == VCHNG) {
+                       vremote(1, yank, 0);
+                       undap1--;
+               }
+
+               /*
+                * Now do the append of the new line in the buffer,
+                * and update the display.  If slowopen
+                * we don't do very much.
+                */
+               vdoappend(genbuf);
+               vundkind = VMANYINS;
+               vcline++;
+               if (state != VISUAL)
+                       vshow(dot, NOLINE);
+               else {
+                       i += LINE(vcline - 1);
+                       vopen(dot, i);
+                       if (value(SLOWOPEN))
+                               vscrap();
+                       else
+                               vsync1(LINE(vcline));
+               }
+               strcLIN(gcursor);
+               *gcursor = 0;
+               cursor = linebuf;
+               vgotoCL(qcolumn(cursor - 1, genbuf));
+       }
+
+       /*
+        * All done with insertion, position the cursor
+        * and sync the screen.
+        */
+       hold = oldhold;
+       if (cursor > linebuf)
+               cursor--;
+       if (state != HARDOPEN)
+               vsyncCL();
+       else if (cursor > linebuf)
+               back1();
+       doomed = 0;
+       wcursor = cursor;
+       vmove();
+}
+
+/*
+ * Subroutine for vgetline to back up a single character position,
+ * backwards around end of lines (vgoto can't hack columns which are
+ * less than 0 in general).
+ */
+back1()
+{
+
+       vgoto(destline - 1, WCOLS + destcol - 1);
+}
+
+/*
+ * Get a line into genbuf after gcursor.
+ * Cnt limits the number of input characters
+ * accepted and is used for handling the replace
+ * single character command.  Aescaped is the location
+ * where we stick a termination indicator (whether we
+ * ended with an ESCAPE or a newline/return.
+ *
+ * We do erase-kill type processing here and also
+ * are careful about the way we do this so that it is
+ * repeatable.  (I.e. so that your kill doesn't happen,
+ * when you repeat an insert if it was escaped with \ the
+ * first time you did it.
+ */
+char *
+vgetline(cnt, gcursor, aescaped)
+       int cnt;
+       register char *gcursor;
+       bool *aescaped;
+{
+       register int c, ch;
+       register char *cp;
+       int x, y, iwhite;
+       char *iglobp;
+       int (*OO)() = Outchar;
+
+       /*
+        * Clear the output state and counters
+        * for autoindent backwards motion (counts of ^D, etc.)
+        * Remember how much white space at beginning of line so
+        * as not to allow backspace over autoindent.
+        */
+       *aescaped = 0;
+       ogcursor = gcursor;
+       flusho();
+       CDCNT = 0;
+       HADUP = 0;
+       HADZERO = 0;
+       gobbled = 0;
+       iwhite = whitecnt(genbuf);
+       iglobp = vglobp;
+
+       /*
+        * Carefully avoid using vinschar in the echo area.
+        */
+       if (splitw)
+               Outchar = vputchar;
+       else {
+               Outchar = vinschar;
+               vprepins();
+       }
+       for (;;) {
+               if (gobblebl)
+                       gobblebl--;
+               if (cnt != 0) {
+                       cnt--;
+                       if (cnt == 0)
+                               goto vadone;
+               }
+               ch = c = getkey() & (QUOTE|TRIM);
+               if (value(MAPINPUT))
+                       while ((ch = map(c, arrows)) != c)
+                               c = ch;
+               if (!iglobp) {
+
+                       /*
+                        * Erase-kill type processing.
+                        * Only happens if we were not reading
+                        * from untyped input when we started.
+                        * Map users erase to ^H, kill to -1 for switch.
+                        */
+                       if (c == tty.sg_erase)
+                               c = CTRL(h);
+                       else if (c == tty.sg_kill)
+                               c = -1;
+                       switch (c) {
+
+                       /*
+                        * ^?           Interrupt drops you back to visual
+                        *              command mode with an unread interrupt
+                        *              still in the input buffer.
+                        *
+                        * ^\           Quit does the same as interrupt.
+                        *              If you are a ex command rather than
+                        *              a vi command this will drop you
+                        *              back to command mode for sure.
+                        */
+                       case ATTN:
+                       case QUIT:
+                               ungetkey(c);
+                               goto vadone;
+
+                       /*
+                        * ^H           Backs up a character in the input.
+                        *
+                        * BUG:         Can't back around line boundaries.
+                        *              This is hard because stuff has
+                        *              already been saved for repeat.
+                        */
+                       case CTRL(h):
+bakchar:
+                               cp = gcursor - 1;
+                               if (cp < ogcursor) {
+                                       if (splitw) {
+                                               /*
+                                                * Backspacing over readecho
+                                                * prompt. Pretend delete but
+                                                * don't beep.
+                                                */
+                                               ungetkey(c);
+                                               goto vadone;
+                                       }
+                                       beep();
+                                       continue;
+                               }
+                               goto vbackup;
+
+                       /*
+                        * ^W           Back up a white/non-white word.
+                        */
+                       case CTRL(w):
+                               wdkind = 1;
+                               for (cp = gcursor; cp > ogcursor && isspace(cp[-1]); cp--)
+                                       continue;
+                               for (c = wordch(cp - 1);
+                                   cp > ogcursor && wordof(c, cp - 1); cp--)
+                                       continue;
+                               goto vbackup;
+
+                       /*
+                        * users kill   Kill input on this line, back to
+                        *              the autoindent.
+                        */
+                       case -1:
+                               cp = ogcursor;
+vbackup:
+                               if (cp == gcursor) {
+                                       beep();
+                                       continue;
+                               }
+                               endim();
+                               *cp = 0;
+                               c = cindent();
+                               vgotoCL(qcolumn(cursor - 1, genbuf));
+                               if (doomed >= 0)
+                                       doomed += c - cindent();
+                               gcursor = cp;
+                               continue;
+
+                       /*
+                        * \            Followed by erase or kill
+                        *              maps to just the erase or kill.
+                        */
+                       case '\\':
+                               x = destcol, y = destline;
+                               putchar('\\');
+                               vcsync();
+                               c = getkey();
+                               if (c == tty.sg_erase || c == tty.sg_kill) {
+                                       vgoto(y, x);
+                                       if (doomed >= 0)
+                                               doomed++;
+                                       goto def;
+                               }
+                               ungetkey(c), c = '\\';
+                               goto noput;
+
+                       /*
+                        * ^Q           Super quote following character
+                        *              Only ^@ is verboten (trapped at
+                        *              a lower level) and \n forces a line
+                        *              split so doesn't really go in.
+                        *
+                        * ^V           Synonym for ^Q
+                        */
+                       case CTRL(q):
+                       case CTRL(v):
+                               x = destcol, y = destline;
+                               putchar('^');
+                               vgoto(y, x);
+                               c = getkey();
+#ifdef TIOCSETC
+                               if (c == ATTN)
+                                       c = nttyc.t_intrc;
+#endif
+                               if (c != NL) {
+                                       if (doomed >= 0)
+                                               doomed++;
+                                       goto def;
+                               }
+                               break;
+                       }
+               }
+
+               /*
+                * If we get a blank not in the echo area
+                * consider splitting the window in the wrapmargin.
+                */
+               if (c == ' ' && !splitw) {
+                       if (gobblebl) {
+                               gobbled = 1;
+                               continue;
+                       }
+                       if (value(WRAPMARGIN) && outcol >= OCOLUMNS - value(WRAPMARGIN)) {
+                               c = NL;
+                               gobblebl = 2;
+                       }
+               }
+               switch (c) {
+
+               /*
+                * ^M           Except in repeat maps to \n.
+                */
+               case CR:
+                       if (vglobp)
+                               goto def;
+                       c = '\n';
+                       /* presto chango ... */
+
+               /*
+                * \n           Start new line.
+                */
+               case NL:
+                       *aescaped = c;
+                       goto vadone;
+
+               /*
+                * escape       End insert unless repeat and more to repeat.
+                */
+               case ESCAPE:
+                       if (lastvgk)
+                               goto def;
+                       goto vadone;
+
+               /*
+                * ^D           Backtab.
+                * ^T           Software forward tab.
+                *
+                *              Unless in repeat where this means these
+                *              were superquoted in.
+                */
+               case CTRL(d):
+               case CTRL(t):
+                       if (vglobp)
+                               goto def;
+                       /* fall into ... */
+
+               /*
+                * ^D|QUOTE     Is a backtab (in a repeated command).
+                */
+               case CTRL(d) | QUOTE:
+                       *gcursor = 0;
+                       cp = vpastwh(genbuf);
+                       c = whitecnt(genbuf);
+                       if (ch == CTRL(t)) {
+                               /*
+                                * ^t just generates new indent replacing
+                                * current white space rounded up to soft
+                                * tab stop increment.
+                                */
+                               if (cp != gcursor)
+                                       /*
+                                        * BUG:         Don't hack ^T except
+                                        *              right after initial
+                                        *              white space.
+                                        */
+                                       continue;
+                               cp = genindent(iwhite = backtab(c + value(SHIFTWIDTH) + 1));
+                               ogcursor = cp;
+                               goto vbackup;
+                       }
+                       /*
+                        * ^D works only if we are at the (end of) the
+                        * generated autoindent.  We count the ^D for repeat
+                        * purposes.
+                        */
+                       if (c == iwhite && c != 0)
+                               if (cp == gcursor) {
+                                       iwhite = backtab(c);
+                                       CDCNT++;
+                                       ogcursor = cp = genindent(iwhite);
+                                       goto vbackup;
+                               } else if (&cp[1] == gcursor &&
+                                   (*cp == '^' || *cp == '0')) {
+                                       /*
+                                        * ^^D moves to margin, then back
+                                        * to current indent on next line.
+                                        *
+                                        * 0^D moves to margin and then
+                                        * stays there.
+                                        */
+                                       HADZERO = *cp == '0';
+                                       ogcursor = cp = genbuf;
+                                       HADUP = 1 - HADZERO;
+                                       CDCNT = 1;
+                                       endim();
+                                       back1();
+                                       vputc(' ');
+                                       goto vbackup;
+                               }
+                       if (vglobp && vglobp - iglobp >= 2 &&
+                           (vglobp[-2] == '^' || vglobp[-2] == '0')
+                           && gcursor == ogcursor + 1)
+                               goto bakchar;
+                       continue;
+
+               default:
+                       /*
+                        * Possibly discard control inputs.
+                        */
+                       if (!vglobp && junk(c)) {
+                               beep();
+                               continue;
+                       }
+def:
+                       putchar(c);
+noput:
+                       if (gcursor > &genbuf[LBSIZE - 2])
+                               error("Line too long");
+                       *gcursor++ = c & TRIM;
+                       vcsync();
+#ifdef LISPCODE
+                       if (value(SHOWMATCH) && !iglobp)
+                               if (c == ')' || c == '}')
+                                       lsmatch(gcursor);
+#endif
+                       continue;
+               }
+       }
+vadone:
+       *gcursor = 0;
+       Outchar = OO;
+       endim();
+       return (gcursor);
+}
+
+int    vgetsplit();
+char   *vsplitpt;
+
+/*
+ * Append the line in buffer at lp
+ * to the buffer after dot.
+ */
+vdoappend(lp)
+       char *lp;
+{
+       register int oing = inglobal;
+
+       vsplitpt = lp;
+       inglobal = 1;
+       ignore(append(vgetsplit, dot));
+       inglobal = oing;
+}
+
+/*
+ * Subroutine for vdoappend to pass to append.
+ */
+vgetsplit()
+{
+
+       if (vsplitpt == 0)
+               return (EOF);
+       strcLIN(vsplitpt);
+       vsplitpt = 0;
+       return (0);
+}
+
+/*
+ * Vmaxrep determines the maximum repetitition factor
+ * allowed that will yield total line length less than
+ * LBSIZE characters and also does hacks for the R command.
+ */
+vmaxrep(ch, cnt)
+       char ch;
+       register int cnt;
+{
+       register int len, replen;
+
+       if (cnt > LBSIZE - 2)
+               cnt = LBSIZE - 2;
+       replen = strlen(genbuf);
+       if (ch == 'R') {
+               len = strlen(cursor);
+               if (replen < len)
+                       len = replen;
+               CP(cursor, cursor + len);
+               vUD2 += len;
+       }
+       len = strlen(linebuf);
+       if (len + cnt * replen <= LBSIZE - 2)
+               return (cnt);
+       cnt = (LBSIZE - 2 - len) / replen;
+       if (cnt == 0) {
+               vsave();
+               error("Line too long");
+       }
+       return (cnt);
+}
diff --git a/usr/src/cmd/ex/ex_vops3.c b/usr/src/cmd/ex/ex_vops3.c
new file mode 100644 (file)
index 0000000..b0e9697
--- /dev/null
@@ -0,0 +1,539 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * Routines to handle structure.
+ * Operations supported are:
+ *     ( ) { } [ ]
+ *
+ * These cover:                LISP            TEXT
+ *     ( )             s-exprs         sentences
+ *     { }             list at same    paragraphs
+ *     [ ]             defuns          sections
+ *
+ * { and } for C used to attempt to do something with matching {}'s, but
+ * I couldn't find definitions which worked intuitively very well, so I
+ * scrapped this.
+ *
+ * The code here is very hard to understand.
+ */
+line   *llimit;
+int    (*lf)();
+
+#ifdef LISPCODE
+int    lindent();
+#endif
+
+bool   wasend;
+
+/*
+ * Find over structure, repeated count times.
+ * Don't go past line limit.  F is the operation to
+ * be performed eventually.  If pastatom then the user said {}
+ * rather than (), implying past atoms in a list (or a paragraph
+ * rather than a sentence.
+ */
+lfind(pastatom, cnt, f, limit)
+       bool pastatom;
+       int cnt, (*f)();
+       line *limit;
+{
+       register int c;
+       register int rc = 0;
+       char save[LBSIZE];
+
+       /*
+        * Initialize, saving the current line buffer state
+        * and computing the limit; a 0 argument means
+        * directional end of file.
+        */
+       wasend = 0;
+       lf = f;
+       strcpy(save, linebuf);
+       if (limit == 0)
+               limit = dir < 0 ? one : dol;
+       llimit = limit;
+       wdot = dot;
+       wcursor = cursor;
+
+       if (pastatom >= 2) {
+               while (cnt > 0 && word(f, cnt))
+                       cnt--;
+               if (pastatom == 3)
+                       eend(f);
+               if (dot == wdot) {
+                       wdot = 0;
+                       if (cursor == wcursor)
+                               rc = -1;
+               }
+       }
+#ifdef LISPCODE
+       else if (!value(LISP)) {
+#else
+       else {
+#endif
+               char *icurs;
+               line *idot;
+
+               if (linebuf[0] == 0) {
+                       do
+                               if (!lnext())
+                                       goto ret;
+                       while (linebuf[0] == 0);
+                       if (dir > 0) {
+                               wdot--;
+                               linebuf[0] = 0;
+                               wcursor = linebuf;
+                               /*
+                                * If looking for sentence, next line
+                                * starts one.
+                                */
+                               if (!pastatom) {
+                                       icurs = wcursor;
+                                       idot = wdot;
+                                       goto begin;
+                               }
+                       }
+               }
+               icurs = wcursor;
+               idot = wdot;
+
+               /*
+                * Advance so as to not find same thing again.
+                */
+               if (dir > 0) {
+                       if (!lnext()) {
+                               rc = -1;
+                               goto ret;
+                       }
+               } else
+                       ignore(lskipa1(""));
+
+               /*
+                * Count times find end of sentence/paragraph.
+                */
+begin:
+               for (;;) {
+                       while (!endsent(pastatom))
+                               if (!lnext())
+                                       goto ret;
+                       if (!pastatom || wcursor == linebuf && endPS())
+                               if (--cnt <= 0)
+                                       break;
+                       if (linebuf[0] == 0) {
+                               do
+                                       if (!lnext())
+                                               goto ret;
+                               while (linebuf[0] == 0);
+                       } else
+                               if (!lnext())
+                                       goto ret;
+               }
+
+               /*
+                * If going backwards, and didn't hit the end of the buffer,
+                * then reverse direction.
+                */
+               if (dir < 0 && (wdot != llimit || wcursor != linebuf)) {
+                       dir = 1;
+                       llimit = dot;
+                       /*
+                        * Empty line needs special treatement.
+                        * If moved to it from other than begining of next line,
+                        * then a sentence starts on next line.
+                        */
+                       if (linebuf[0] == 0 && !pastatom && 
+                          (wdot != dot - 1 || cursor != linebuf)) {
+                               lnext();
+                               goto ret;
+                       }
+               }
+
+               /*
+                * If we are not at a section/paragraph division,
+                * advance to next.
+                */
+               if (wcursor == icurs && wdot == idot || wcursor != linebuf || !endPS())
+                       ignore(lskipa1(""));
+       }
+#ifdef LISPCODE
+       else {
+               c = *wcursor;
+               /*
+                * Startup by skipping if at a ( going left or a ) going
+                * right to keep from getting stuck immediately.
+                */
+               if (dir < 0 && c == '(' || dir > 0 && c == ')') {
+                       if (!lnext()) {
+                               rc = -1;
+                               goto ret;
+                       }
+               }
+               /*
+                * Now chew up repitition count.  Each time around
+                * if at the beginning of an s-exp (going forwards)
+                * or the end of an s-exp (going backwards)
+                * skip the s-exp.  If not at beg/end resp, then stop
+                * if we hit a higher level paren, else skip an atom,
+                * counting it unless pastatom.
+                */
+               while (cnt > 0) {
+                       c = *wcursor;
+                       if (dir < 0 && c == ')' || dir > 0 && c == '(') {
+                               if (!lskipbal("()"))
+                                       goto ret;
+                               /*
+                                * Unless this is the last time going
+                                * backwards, skip past the matching paren
+                                * so we don't think it is a higher level paren.
+                                */
+                               if (dir < 0 && cnt == 1)
+                                       goto ret;
+                               if (!lnext() || !ltosolid())
+                                       goto ret;
+                               --cnt;
+                       } else if (dir < 0 && c == '(' || dir > 0 && c == ')')
+                               /* Found a higher level paren */
+                               goto ret;
+                       else {
+                               if (!lskipatom())
+                                       goto ret;
+                               if (!pastatom)
+                                       --cnt;
+                       }
+               }
+       }
+#endif
+ret:
+       strcLIN(save);
+       return (rc);
+}
+
+/*
+ * Is this the end of a sentence?
+ */
+endsent(pastatom)
+       bool pastatom;
+{
+       register char *cp = wcursor;
+       register int c, d;
+
+       /*
+        * If this is the beginning of a line, then
+        * check for the end of a paragraph or section.
+        */
+       if (cp == linebuf)
+               return (endPS());
+
+       /*
+        * Sentences end with . ! ? not at the beginning
+        * of the line, and must be either at the end of the line,
+        * or followed by 2 spaces.  Any number of intervening ) ] ' "
+        * characters are allowed.
+        */
+       if (!any(c = *cp, ".!?"))
+               goto tryps;
+       do
+               if ((d = *++cp) == 0)
+                       return (1);
+       while (any(d, ")]'"));
+       if (*cp == 0 || *cp++ == ' ' && *cp == ' ')
+               return (1);
+tryps:
+       if (cp[1] == 0)
+               return (endPS());
+       return (0);
+}
+
+/*
+ * End of paragraphs/sections are respective
+ * macros as well as blank lines and form feeds.
+ */
+endPS()
+{
+
+       return (linebuf[0] == 0 ||
+               isa(svalue(PARAGRAPHS)) || isa(svalue(SECTIONS)));
+           
+}
+
+#ifdef LISPCODE
+lindent(addr)
+       line *addr;
+{
+       register int i;
+       char *swcurs = wcursor;
+       line *swdot = wdot;
+
+again:
+       if (addr > one) {
+               register char *cp;
+               register int cnt = 0;
+
+               addr--;
+               getline(*addr);
+               for (cp = linebuf; *cp; cp++)
+                       if (*cp == '(')
+                               cnt++;
+                       else if (*cp == ')')
+                               cnt--;
+               cp = vpastwh(linebuf);
+               if (*cp == 0)
+                       goto again;
+               if (cnt == 0)
+                       return (whitecnt(linebuf));
+               addr++;
+       }
+       wcursor = linebuf;
+       linebuf[0] = 0;
+       wdot = addr;
+       dir = -1;
+       llimit = one;
+       lf = lindent;
+       if (!lskipbal("()"))
+               i = 0;
+       else if (wcursor == linebuf)
+               i = 2;
+       else {
+               register char *wp = wcursor;
+
+               dir = 1;
+               llimit = wdot;
+               if (!lnext() || !ltosolid() || !lskipatom()) {
+                       wcursor = wp;
+                       i = 1;
+               } else
+                       i = 0;
+               i += column(wcursor) - 1;
+               if (!inopen)
+                       i--;
+       }
+       wdot = swdot;
+       wcursor = swcurs;
+       return (i);
+}
+#endif
+
+lmatchp(addr)
+       line *addr;
+{
+       register int i;
+       register char *parens, *cp;
+
+       for (cp = cursor; !any(*cp, "({)}");)
+               if (*cp++ == 0)
+                       return (0);
+       lf = 0;
+       parens = any(*cp, "()") ? "()" : "{}";
+       if (*cp == parens[1]) {
+               dir = -1;
+               llimit = one;
+       } else {
+               dir = 1;
+               llimit = dol;
+       }
+       if (addr)
+               llimit = addr;
+       if (splitw)
+               llimit = dot;
+       wcursor = cp;
+       wdot = dot;
+       i = lskipbal(parens);
+       return (i);
+}
+
+lsmatch(cp)
+       char *cp;
+{
+       char save[LBSIZE];
+       register char *sp = save;
+       register char *scurs = cursor;
+
+       wcursor = cp;
+       strcpy(sp, linebuf);
+       *wcursor = 0;
+       strcpy(cursor, genbuf);
+       cursor = strend(linebuf) - 1;
+       if (lmatchp(dot - vcline)) {
+               register int i = insmode;
+               register int c = outcol;
+               register int l = outline;
+
+               if (!MI)
+                       endim();
+               vgoto(splitw ? WECHO : LINE(wdot - llimit), column(wcursor) - 1);
+               flush();
+               sleep(1);
+               vgoto(l, c);
+               if (i)
+                       goim();
+       }
+       strcLIN(sp);
+       wdot = 0;
+       wcursor = 0;
+       cursor = scurs;
+}
+
+ltosolid()
+{
+
+       return (ltosol1("()"));
+}
+
+ltosol1(parens)
+       register char *parens;
+{
+       register char *cp;
+
+       if (*parens && !*wcursor && !lnext())
+               return (0);
+       while (isspace(*wcursor) || (*wcursor == 0 && *parens))
+               if (!lnext())
+                       return (0);
+       if (any(*wcursor, parens) || dir > 0)
+               return (1);
+       for (cp = wcursor; cp > linebuf; cp--)
+               if (isspace(cp[-1]) || any(cp[-1], parens))
+                       break;
+       wcursor = cp;
+       return (1);
+}
+
+lskipbal(parens)
+       register char *parens;
+{
+       register int level = dir;
+       register int c;
+
+       do {
+               if (!lnext())
+                       return (0);
+               c = *wcursor;
+               if (c == parens[1])
+                       level--;
+               else if (c == parens[0])
+                       level++;
+       } while (level);
+       return (1);
+}
+
+lskipatom()
+{
+
+       return (lskipa1("()"));
+}
+
+lskipa1(parens)
+       register char *parens;
+{
+       register int c;
+
+       for (;;) {
+               if (dir < 0 && wcursor == linebuf) {
+                       if (!lnext())
+                               return (0);
+                       break;
+               }
+               c = *wcursor;
+               if (c && (isspace(c) || any(c, parens)))
+                       break;
+               if (!lnext())
+                       return (0);
+               if (dir > 0 && wcursor == linebuf)
+                       break;
+       }
+       return (ltosol1(parens));
+}
+
+lnext()
+{
+
+       if (dir > 0) {
+               if (*wcursor)
+                       wcursor++;
+               if (*wcursor)
+                       return (1);
+               if (wdot >= llimit) {
+                       if (wcursor > linebuf)
+                               wcursor--;
+                       return (0);
+               }
+               wdot++;
+               getline(*wdot);
+               wcursor = linebuf;
+               return (1);
+       } else {
+               --wcursor;
+               if (wcursor >= linebuf)
+                       return (1);
+#ifdef LISPCODE
+               if (lf == lindent && linebuf[0] == '(')
+                       llimit = wdot;
+#endif
+               if (wdot <= llimit) {
+                       wcursor = linebuf;
+                       return (0);
+               }
+               wdot--;
+               getline(*wdot);
+               wcursor = linebuf[0] == 0 ? linebuf : strend(linebuf) - 1;
+               return (1);
+       }
+}
+
+lbrack(c, f)
+       register int c;
+       int (*f)();
+{
+       register line *addr;
+
+       addr = dot;
+       for (;;) {
+               addr += dir;
+               if (addr < one || addr > dol) {
+                       addr -= dir;
+                       break;
+               }
+               getline(*addr);
+               if (linebuf[0] == '{' ||
+#ifdef LISPCODE
+                   value(LISP) && linebuf[0] == '(' ||
+#endif
+                   isa(svalue(SECTIONS))) {
+                       if (c == ']' && f != vmove) {
+                               addr--;
+                               getline(*addr);
+                       }
+                       break;
+               }
+               if (c == ']' && f != vmove && linebuf[0] == '}')
+                       break;
+       }
+       if (addr == dot)
+               return (0);
+       if (f != vmove)
+               wcursor = c == ']' ? strend(linebuf) : linebuf;
+       else
+               wcursor = 0;
+       wdot = addr;
+       vmoving = 0;
+       return (1);
+}
+
+isa(cp)
+       register char *cp;
+{
+
+       if (linebuf[0] != '.')
+               return (0);
+       for (; cp[0] && cp[1]; cp += 2)
+               if (linebuf[1] == cp[0]) {
+                       if (linebuf[2] == cp[1])
+                               return (1);
+                       if (linebuf[2] == 0 && cp[1] == ' ')
+                               return (1);
+               }
+       return (0);
+}
diff --git a/usr/src/cmd/ex/ex_vput.c b/usr/src/cmd/ex/ex_vput.c
new file mode 100644 (file)
index 0000000..cadd33a
--- /dev/null
@@ -0,0 +1,1331 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * Deal with the screen, clearing, cursor positioning, putting characters
+ * into the screen image, and deleting characters.
+ * Really hard stuff here is utilizing insert character operations
+ * on intelligent terminals which differs widely from terminal to terminal.
+ */
+vclear()
+{
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "------\nvclear\n");
+#endif
+       tputs(CL, LINES, putch);
+       destcol = 0;
+       outcol = 0;
+       destline = 0;
+       outline = 0;
+       if (inopen)
+               vclrbyte(vtube0, WCOLS * (WECHO - ZERO + 1));
+}
+
+/*
+ * Clear memory.
+ */
+vclrbyte(cp, i)
+       register char *cp;
+       register int i;
+{
+
+       if (i > 0)
+               do
+                       *cp++ = 0;
+               while (--i != 0);
+}
+
+/*
+ * Clear a physical display line, high level.
+ */
+vclrlin(l, tp)
+       int l;
+       line *tp;
+{
+
+       vigoto(l, 0);
+       if ((hold & HOLDAT) == 0)
+               putchar(tp > dol ? ((UPPERCASE || HZ) ? '^' : '~') : '@');
+       if (state == HARDOPEN)
+               sethard();
+       vclreol();
+}
+
+/*
+ * Clear to the end of the current physical line
+ */
+vclreol()
+{
+       register int i, j;
+       register char *tp;
+
+       if (destcol == WCOLS)
+               return;
+       destline += destcol / WCOLS;
+       destcol %= WCOLS;
+       if (destline < 0 || destline > WECHO)
+               error("Internal error: vclreol");
+       i = WCOLS - destcol;
+       tp = vtube[destline] + destcol;
+       if (CE) {
+               if (IN && *tp || !ateopr()) {
+                       vcsync();
+                       vputp(CE, 1);
+               }
+               vclrbyte(tp, i);
+               return;
+       }
+       if (*tp == 0)
+               return;
+       while (i > 0 && (j = *tp & (QUOTE|TRIM))) {
+               if (j != ' ' && (j & QUOTE) == 0) {
+                       destcol = WCOLS - i;
+                       vputchar(' ');
+               }
+               --i, *tp++ = 0;
+       }
+}
+
+/*
+ * Clear the echo line.
+ * If didphys then its been cleared physically (as
+ * a side effect of a clear to end of display, e.g.)
+ * so just do it logically.
+ * If work here is being held off, just remember, in
+ * heldech, if work needs to be done, don't do anything.
+ */
+vclrech(didphys)
+       bool didphys;
+{
+
+       if (Peekkey == ATTN)
+               return;
+       if (hold & HOLDECH) {
+               heldech = !didphys;
+               return;
+       }
+       if (!didphys && (CD || CE)) {
+               splitw++;
+               /*
+                * If display is retained below, then MUST use CD or CE
+                * since we don't really know whats out there.
+                * Vigoto might decide (incorrectly) to do nothing.
+                */
+               if (DB)
+                       vgoto(WECHO, 0), vputp(CD ? CD : CE, 1);
+               else
+                       vigoto(WECHO, 0), vclreol();
+               splitw = 0;
+               didphys = 1;
+       }
+       if (didphys)
+               vclrbyte(vtube[WECHO], WCOLS);
+       heldech = 0;
+}
+
+/*
+ * Fix the echo area for use, setting
+ * the state variable splitw so we wont rollup
+ * when we move the cursor there.
+ */
+fixech()
+{
+
+       splitw++;
+       if (state != VISUAL && state != CRTOPEN) {
+               vclean();
+               vcnt = 0;
+       }
+       vgoto(WECHO, 0); flusho();
+}
+
+/*
+ * Put the cursor ``before'' cp.
+ */
+vcursbef(cp)
+       register char *cp;
+{
+
+       if (cp <= linebuf)
+               vgotoCL(value(NUMBER) << 3);
+       else
+               vgotoCL(column(cp - 1) - 1);
+}
+
+/*
+ * Put the cursor ``at'' cp.
+ */
+vcursat(cp)
+       register char *cp;
+{
+
+       if (cp <= linebuf && linebuf[0] == 0)
+               vgotoCL(value(NUMBER) << 3);
+       else
+               vgotoCL(column(cp - 1));
+}
+
+/*
+ * Put the cursor ``after'' cp.
+ */
+vcursaft(cp)
+       register char *cp;
+{
+
+       vgotoCL(column(cp));
+}
+
+/*
+ * Fix the cursor to be positioned in the correct place
+ * to accept a command.
+ */
+vfixcurs()
+{
+
+       vsetcurs(cursor);
+}
+
+/*
+ * Compute the column position implied by the cursor at ``nc'',
+ * and move the cursor there.
+ */
+vsetcurs(nc)
+       register char *nc;
+{
+       register int col;
+
+       col = column(nc);
+       if (linebuf[0])
+               col--;
+       vgotoCL(col);
+       cursor = nc;
+}
+
+/*
+ * Move the cursor invisibly, i.e. only remember to do it.
+ */
+vigoto(y, x)
+       int y, x;
+{
+
+       destline = y;
+       destcol = x;
+}
+
+/*
+ * Move the cursor to the position implied by any previous
+ * vigoto (or low level hacking with destcol/destline as in readecho).
+ */
+vcsync()
+{
+
+       vgoto(destline, destcol);
+}
+
+/*
+ * Goto column x of the current line.
+ */
+vgotoCL(x)
+       register int x;
+{
+
+       if (splitw)
+               vgoto(WECHO, x);
+       else
+               vgoto(LINE(vcline), x);
+}
+
+/*
+ * Invisible goto column x of current line.
+ */
+vigotoCL(x)
+       register int x;
+{
+
+       if (splitw)
+               vigoto(WECHO, x);
+       else
+               vigoto(LINE(vcline), x);
+}
+
+/*
+ * Move cursor to line y, column x, handling wraparound and scrolling.
+ */
+vgoto(y, x)
+       register int y, x;
+{
+       register char *tp;
+       register int c;
+
+       /*
+        * Fold the possibly too large value of x.
+        */
+       if (x >= WCOLS) {
+               y += x / WCOLS;
+               x %= WCOLS;
+       }
+       if (y < 0)
+               error("Internal error: vgoto");
+       if (outcol >= WCOLS) {
+               if (AM) {
+                       outline += outcol / WCOLS;
+                       outcol %= WCOLS;
+               } else
+                       outcol = WCOLS - 1;
+       }
+
+       /*
+        * In a hardcopy or glass crt open, print the stuff
+        * implied by a motion, or backspace.
+        */
+       if (state == HARDOPEN || state == ONEOPEN) {
+               if (y != outline)
+                       error("Line too long for open");
+               if (x + 1 < outcol - x || (outcol > x && !BS))
+                       destcol = 0, fgoto();
+               tp = vtube[WBOT] + outcol;
+               while (outcol != x)
+                       if (outcol < x) {
+                               if (*tp == 0)
+                                       *tp = ' ';
+                               c = *tp++ & TRIM;
+                               vputc(c && (!OS || EO) ? c : ' '), outcol++;
+                       } else {
+                               if (BC)
+                                       vputp(BC, 0);
+                               else
+                                       vputc('\b');
+                               outcol--;
+                       }
+               destcol = outcol = x;
+               destline = outline;
+               return;
+       }
+
+       /*
+        * If the destination position implies a scroll, do it.
+        */
+       destline = y;
+       if (destline > WBOT && (!splitw || destline > WECHO)) {
+               endim();
+               vrollup(destline);
+       }
+
+       /*
+        * If there really is a motion involved, do it.
+        * The check here is an optimization based on profiling.
+        */
+       destcol = x;
+       if ((destline - outline) * WCOLS != destcol - outcol) {
+               if (!MI)
+                       endim();
+               fgoto();
+       }
+}
+
+/*
+ * This is the hardest code in the editor, and deals with insert modes
+ * on different kinds of intelligent terminals.  The complexity is due
+ * to the cross product of three factors:
+ *
+ *     1. Lines may display as more than one segment on the screen.
+ *     2. There are 2 kinds of intelligent terminal insert modes.
+ *     3. Tabs squash when you insert characters in front of them,
+ *        in a way in which current intelligent terminals don't handle.
+ *
+ * The two kinds of terminals are typified by the DM2500 or HP2645 for
+ * one and the CONCEPT-100 or the FOX for the other.
+ *
+ * The first (HP2645) kind has an insert mode where the characters
+ * fall off the end of the line and the screen is shifted rigidly
+ * no matter how the display came about.
+ *
+ * The second (CONCEPT-100) kind comes from terminals which are designed
+ * for forms editing and which distinguish between blanks and ``spaces''
+ * on the screen, spaces being like blank, but never having had
+ * and data typed into that screen position (since, e.g. a clear operation
+ * like clear screen).  On these terminals, when you insert a character,
+ * the characters from where you are to the end of the screen shift
+ * over till a ``space'' is found, and the null character there gets
+ * eaten up.
+ *
+ *
+ * The code here considers the line as consisting of several parts
+ * the first part is the ``doomed'' part, i.e. a part of the line
+ * which is being typed over.  Next comes some text up to the first
+ * following tab.  The tab is the next segment of the line, and finally
+ * text after the tab.
+ *
+ * We have to consider each of these segments and the effect of the
+ * insertion of a character on them.  On terminals like HP2645's we
+ * must simulate a multi-line insert mode using the primitive one
+ * line insert mode.  If we are inserting in front of a tab, we have
+ * to either delete characters from the tab or insert white space
+ * (when the tab reaches a new spot where it gets larger) before we
+ * insert the new character.
+ *
+ * On a terminal like a CONCEPT our strategy is to make all
+ * blanks be displayed, while trying to keep the screen having ``spaces''
+ * for portions of tabs.  In this way the terminal hardward does some
+ * of the hacking for compression of tabs, although this tends to
+ * disappear as you work on the line and spaces change into blanks.
+ *
+ * There are a number of boundary conditions (like typing just before
+ * the first following tab) where we can avoid a lot of work.  Most
+ * of them have to be dealt with explicitly because performance is
+ * much, much worse if we don't.
+ *
+ * A final thing which is hacked here is two flavors of insert mode.
+ * Datamedia's do this by an insert mode which you enter and leave
+ * and by having normal motion character operate differently in this
+ * mode, notably by having a newline insert a line on the screen in
+ * this mode.  This generally means it is unsafe to move around
+ * the screen ignoring the fact that we are in this mode.
+ * This is possible on some terminals, and wins big (e.g. HP), so
+ * we encode this as a ``can move in insert capability'' mi,
+ * and terminals which have it can do insert mode with much less
+ * work when tabs are present following the cursor on the current line.
+ */
+
+/*
+ * Routine to expand a tab, calling the normal Outchar routine
+ * to put out each implied character.  Note that we call outchar
+ * with a QUOTE.  We use QUOTE internally to represent a position
+ * which is part of the expansion of a tab.
+ */
+vgotab()
+{
+       register int i = (LINE(vcline) - destline) * WCOLS + destcol;
+
+       do
+               (*Outchar)(QUOTE);
+       while (++i % value(TABSTOP));
+}
+
+/*
+ * Variables for insert mode.
+ */
+int    linend;                 /* The column position of end of line */
+int    tabstart;               /* Column of start of first following tab */
+int    tabend;                 /* Column of end of following tabs */
+int    tabsize;                /* Size of the following tabs */
+int    tabslack;               /* Number of ``spaces'' in following tabs */
+int    inssiz;                 /* Number of characters to be inserted */
+int    inscol;                 /* Column where insertion is taking place */
+int    shft;                   /* Amount tab expansion shifted rest of line */
+int    slakused;               /* This much of tabslack will be used up */
+
+/*
+ * This routine MUST be called before insert mode is run,
+ * and brings all segments of the current line to the top
+ * of the screen image buffer so it is easier for us to
+ * maniuplate them.
+ */
+vprepins()
+{
+       register int i;
+       register char *cp = vtube0;
+
+       for (i = 0; i < DEPTH(vcline); i++) {
+               vmaktop(LINE(vcline) + i, cp);
+               cp += WCOLS;
+       }
+}
+
+vmaktop(p, cp)
+       register int p;
+       char *cp;
+{
+       register int i;
+       char temp[TUBECOLS];
+
+       if (vtube[p] == cp)
+               return;
+       for (i = ZERO; i <= WECHO; i++)
+               if (vtube[i] == cp) {
+                       copy(temp, vtube[i], WCOLS);
+                       copy(vtube[i], vtube[p], WCOLS);
+                       copy(vtube[p], temp, WCOLS);
+                       vtube[i] = vtube[p];
+                       vtube[p] = cp;
+                       return;
+               }
+       error("Line too long");
+}
+
+/*
+ * Insert character c at current cursor position.
+ * Multi-character inserts occur only as a result
+ * of expansion of tabs (i.e. inssize == 1 except
+ * for tabs) and code assumes this in several place
+ * to make life simpler.
+ */
+vinschar(c)
+       char c;
+{
+       register int i;
+       register char *tp;
+
+       if ((!IM || !EI) && ((hold & HOLDQIK) || !value(REDRAW) || value(SLOWOPEN))) {
+               /*
+                * Don't want to try to use terminal
+                * insert mode, or to try to fake it.
+                * Just put the character out; the screen
+                * will probably be wrong but we will fix it later.
+                */
+               if (c == '\t') {
+                       vgotab();
+                       return;
+               }
+               vputchar(c);
+               if (DEPTH(vcline) * WCOLS + !value(REDRAW) >
+                   (destline - LINE(vcline)) * WCOLS + destcol)
+                       return;
+               /*
+                * The next line is about to be clobbered
+                * make space for another segment of this line
+                * (on an intelligent terminal) or just remember
+                * that next line was clobbered (on a dumb one
+                * if we don't care to redraw the tail.
+                */
+               if (AL) {
+                       vnpins(0);
+               } else {
+                       c = LINE(vcline) + DEPTH(vcline);
+                       if (c < LINE(vcline + 1) || c > WBOT)
+                               return;
+                       i = destcol;
+                       vinslin(c, 1, vcline);
+                       DEPTH(vcline)++;
+                       vigoto(c, i);
+                       vprepins();
+               }
+               return;
+       }
+       /*
+        * Compute the number of positions in the line image of the
+        * current line.  This is done from the physical image
+        * since that is faster.  Note that we have no memory
+        * from insertion to insertion so that routines which use
+        * us don't have to worry about moving the cursor around.
+        */
+       if (*vtube0 == 0)
+               linend = 0;
+       else {
+               /*
+                * Search backwards for a non-null character
+                * from the end of the displayed line.
+                */
+               i = WCOLS * DEPTH(vcline);
+               if (i == 0)
+                       i = WCOLS;
+               tp = vtube0 + i;
+               while (*--tp == 0)
+                       if (--i == 0)
+                               break;
+               linend = i;
+       }
+
+       /*
+        * We insert at a position based on the physical location
+        * of the output cursor.
+        */
+       inscol = destcol + (destline - LINE(vcline)) * WCOLS;
+       if (c == '\t') {
+               /*
+                * Characters inserted from a tab must be
+                * remembered as being part of a tab, but we can't
+                * use QUOTE here since we really need to print blanks.
+                * QUOTE|' ' is the representation of this.
+                */
+               inssiz = value(TABSTOP) - inscol % value(TABSTOP);
+               c = ' ' | QUOTE;
+       } else
+               inssiz = 1;
+
+       /*
+        * If the text to be inserted is less than the number
+        * of doomed positions, then we don't need insert mode,
+        * rather we can just typeover.
+        */
+       if (inssiz <= doomed) {
+               endim();
+               if (inscol != linend)
+                       doomed -= inssiz;
+               do
+                       vputchar(c);
+               while (--inssiz);
+               return;
+       }
+
+       /*
+        * Have to really do some insertion, thus
+        * stake out the bounds of the first following
+        * group of tabs, computing starting position,
+        * ending position, and the number of ``spaces'' therein
+        * so we can tell how much it will squish.
+        */
+       tp = vtube0 + inscol;
+       for (i = inscol; i < linend; i++)
+               if (*tp++ & QUOTE) {
+                       --tp;
+                       break;
+               }
+       tabstart = tabend = i;
+       tabslack = 0;
+       while (tabend < linend) {
+               i = *tp++;
+               if ((i & QUOTE) == 0)
+                       break;
+               if ((i & TRIM) == 0)
+                       tabslack++;
+               tabsize++;
+               tabend++;
+       }
+       tabsize = tabend - tabstart;
+
+       /*
+        * For HP's and DM's, e.g. tabslack has no meaning.
+        */
+       if (!IN)
+               tabslack = 0;
+#ifdef IDEBUG
+       if (trace) {
+               fprintf(trace, "inscol %d, inssiz %d, tabstart %d, ",
+                       inscol, inssiz, tabstart);
+               fprintf(trace, "tabend %d, tabslack %d, linend %d\n",
+                       tabend, tabslack, linend);
+       }
+#endif
+
+       /*
+        * The real work begins.
+        */
+       slakused = 0;
+       shft = 0;
+       if (tabsize) {
+               /*
+                * There are tabs on this line.
+                * If they need to expand, then the rest of the line
+                * will have to be shifted over.  In this case,
+                * we will need to make sure there are no ``spaces''
+                * in the rest of the line (on e.g. CONCEPT-100)
+                * and then grab another segment on the screen if this
+                * line is now deeper.  We then do the shift
+                * implied by the insertion.
+                */
+               if (inssiz >= doomed + value(TABSTOP) - tabstart % value(TABSTOP)) {
+                       if (IN)
+                               vrigid();
+                       vneedpos(value(TABSTOP));
+                       vishft();
+               }
+       } else if (inssiz > doomed)
+               /*
+                * No tabs, but line may still get deeper.
+                */
+               vneedpos(inssiz - doomed);
+       /*
+        * Now put in the inserted characters.
+        */
+       viin(c);
+
+       /*
+        * Now put the cursor in its final resting place.
+        */
+       destline = LINE(vcline);
+       destcol = inscol + inssiz;
+       vcsync();
+}
+
+/*
+ * Rigidify the rest of the line after the first
+ * group of following tabs, typing blanks over ``spaces''.
+ */
+vrigid()
+{
+       register int col;
+       register char *tp = vtube0 + tabend;
+
+       for (col = tabend; col < linend; col++)
+               if ((*tp++ & TRIM) == 0) {
+                       endim();
+                       vgotoCL(col);
+                       vputchar(' ' | QUOTE);
+               }
+}
+
+/*
+ * We need cnt more positions on this line.
+ * Open up new space on the screen (this may in fact be a
+ * screen rollup).
+ *
+ * On a dumb terminal we may infact redisplay the rest of the
+ * screen here brute force to keep it pretty.
+ */
+vneedpos(cnt)
+       int cnt;
+{
+       register int d = DEPTH(vcline);
+       register int rmdr = d * WCOLS - linend;
+
+       if (cnt <= rmdr - IN)
+               return;
+       endim();
+       vnpins(1);
+}
+
+vnpins(dosync)
+       int dosync;
+{
+       register int d = DEPTH(vcline);
+       register int e;
+
+       e = LINE(vcline) + DEPTH(vcline);
+       if (e < LINE(vcline + 1)) {
+               vigoto(e, 0);
+               vclreol();
+               return;
+       }
+       DEPTH(vcline)++;
+       if (e < WECHO) {
+               e = vglitchup(vcline, d);
+               vigoto(e, 0); vclreol();
+               if (dosync) {
+                       Outchar = vputchar;
+                       vsync(e + 1);
+                       Outchar = vinschar;
+               }
+       } else {
+               vup1();
+               vigoto(WBOT, 0);
+               vclreol();
+       }
+       vprepins();
+}
+
+/*
+ * Do the shift of the next tabstop implied by
+ * insertion so it expands.
+ */
+vishft()
+{
+       int tshft = 0;
+       int j;
+       register int i;
+       register char *tp = vtube0;
+       register char *up;
+       short oldhold = hold;
+
+       shft = value(TABSTOP);
+       hold |= HOLDPUPD;
+       if (!IM && !EI) {
+               /*
+                * Dumb terminals are easy, we just have
+                * to retype the text.
+                */
+               vigotoCL(tabend + shft);
+               up = tp + tabend;
+               for (i = tabend; i < linend; i++)
+                       vputchar(*up++);
+       } else if (IN) {
+               /*
+                * CONCEPT-like terminals do most of the work for us,
+                * we don't have to muck with simulation of multi-line
+                * insert mode.  Some of the shifting may come for free
+                * also if the tabs don't have enough slack to take up
+                * all the inserted characters.
+                */
+               i = shft;
+               slakused = inssiz - doomed;
+               if (slakused > tabslack) {
+                       i -= slakused - tabslack;
+                       slakused -= tabslack;
+               }
+               if (i > 0 && tabend != linend) {
+                       tshft = i;
+                       vgotoCL(tabend);
+                       goim();
+                       do
+                               vputchar(' ' | QUOTE);
+                       while (--i);
+               }
+       } else {
+               /*
+                * HP and Datamedia type terminals have to have multi-line
+                * insert faked.  Hack each segment after where we are
+                * (going backwards to where we are.)  We then can
+                * hack the segment where the end of the first following
+                * tab group is.
+                */
+               for (j = DEPTH(vcline) - 1; j > (tabend + shft) / WCOLS; j--) {
+                       vgotoCL(j * WCOLS);
+                       goim();
+                       up = tp + j * WCOLS - shft;
+                       i = shft;
+                       do
+                               vputchar(*up++);
+                       while (--i);
+               }
+               vigotoCL(tabstart);
+               i = shft - (inssiz - doomed);
+               if (i > 0) {
+                       tabslack = inssiz - doomed;
+                       vcsync();
+                       goim();
+                       do
+                               vputchar(' ');
+                       while (--i);
+               }
+       }
+       /*
+        * Now do the data moving in the internal screen
+        * image which is common to all three cases.
+        */
+       tp += linend;
+       up = tp + shft;
+       i = linend - tabend;
+       if (i > 0)
+               do
+                       *--up = *--tp;
+               while (--i);
+       if (IN && tshft) {
+               i = tshft;
+               do
+                       *--up = ' ' | QUOTE;
+               while (--i);
+       }
+       hold = oldhold;
+}
+
+/*
+ * Now do the insert of the characters (finally).
+ */
+viin(c)
+       char c;
+{
+       register char *tp, *up;
+       register int i, j;
+       register bool noim = 0;
+       int remdoom;
+       short oldhold = hold;
+
+       hold |= HOLDPUPD;
+       if (tabsize && (IM && EI) && inssiz - doomed > tabslack)
+               /*
+                * There is a tab out there which will be affected
+                * by the insertion since there aren't enough doomed
+                * characters to take up all the insertion and we do
+                * have insert mode capability.
+                */
+               if (inscol + doomed == tabstart) {
+                       /*
+                        * The end of the doomed characters sits right at the
+                        * start of the tabs, then we don't need to use insert
+                        * mode; unless the tab has already been expanded
+                        * in which case we MUST use insert mode.
+                        */
+                       slakused = 0;
+                       noim = !shft;
+               } else {
+                       /*
+                        * The last really special case to handle is case
+                        * where the tab is just sitting there and doesn't
+                        * have enough slack to let the insertion take
+                        * place without shifting the rest of the line
+                        * over.  In this case we have to go out and
+                        * delete some characters of the tab before we start
+                        * or the answer will be wrong, as the rest of the
+                        * line will have been shifted.  This code means
+                        * that terminals with only insert chracter (no
+                        * delete character) won't work correctly.
+                        */
+                       i = inssiz - doomed - tabslack - slakused;
+                       i %= value(TABSTOP);
+                       if (i > 0) {
+                               vgotoCL(tabstart);
+                               godm();
+                               for (i = inssiz - doomed - tabslack; i > 0; i--)
+                                       vputp(DC, DEPTH(vcline));
+                               enddm();
+                       }
+               }
+
+       /* 
+        * Now put out the characters of the actual insertion.
+        */
+       vigotoCL(inscol);
+       remdoom = doomed;
+       for (i = inssiz; i > 0; i--) {
+               if (remdoom > 0) {
+                       remdoom--;
+                       endim();
+               } else if (noim)
+                       endim();
+               else if (IM && EI) {
+                       vcsync();
+                       goim();
+               }
+               vputchar(c);
+       }
+
+       if (!IM || !EI) {
+               /*
+                * We are a dumb terminal; brute force update
+                * the rest of the line; this is very much an n^^2 process,
+                * and totally unreasonable at low speed.
+                *
+                * You asked for it, you get it.
+                */
+               tp = vtube0 + inscol + doomed;
+               for (i = inscol + doomed; i < tabstart; i++)
+                       vputchar(*tp++);
+               hold = oldhold;
+               vigotoCL(tabstart + inssiz - doomed);
+               for (i = tabsize - (inssiz - doomed) + shft; i > 0; i--)
+                       vputchar(' ' | QUOTE);
+       } else {
+               if (!IN) {
+                       /*
+                        * On terminals without multi-line
+                        * insert in the hardware, we must go fix the segments
+                        * between the inserted text and the following
+                        * tabs, if they are on different lines.
+                        *
+                        * Aaargh.
+                        */
+                       tp = vtube0;
+                       for (j = (inscol + inssiz - 1) / WCOLS + 1;
+                           j <= (tabstart + inssiz - doomed - 1) / WCOLS; j++) {
+                               vgotoCL(j * WCOLS);
+                               i = inssiz - doomed;
+                               up = tp + j * WCOLS - i;
+                               goim();
+                               do
+                                       vputchar(*up++);
+                               while (--i && *up);
+                       }
+               } else {
+                       /*
+                        * On terminals with multi line inserts,
+                        * life is simpler, just reflect eating of
+                        * the slack.
+                        */
+                       tp = vtube0 + tabend;
+                       for (i = tabsize - (inssiz - doomed); i >= 0; i--) {
+                               if ((*--tp & (QUOTE|TRIM)) == QUOTE) {
+                                       --tabslack;
+                                       if (tabslack >= slakused)
+                                               continue;
+                               }
+                               *tp = ' ' | QUOTE;
+                       }
+               }
+               /*
+                * Blank out the shifted positions to be tab positions.
+                */
+               if (shft) {
+                       tp = vtube0 + tabend + shft;
+                       for (i = tabsize - (inssiz - doomed) + shft; i > 0; i--)
+                               if ((*--tp & QUOTE) == 0)
+                                       *tp = ' ' | QUOTE;
+               }
+       }
+
+       /*
+        * Finally, complete the screen image update
+        * to reflect the insertion.
+        */
+       hold = oldhold;
+       tp = vtube0 + tabstart; up = tp + inssiz - doomed;
+       for (i = tabstart; i > inscol + doomed; i--)
+               *--up = *--tp;
+       for (i = inssiz; i > 0; i--)
+               *--up = c;
+       doomed = 0;
+}
+
+/*
+ * Go into ``delete mode''.  If the
+ * sequence which goes into delete mode
+ * is the same as that which goes into insert
+ * mode, then we are in delete mode already.
+ */
+godm()
+{
+
+       if (insmode) {
+               if (eq(DM, IM))
+                       return;
+               endim();
+       }
+       vputp(DM, 0);
+}
+
+/*
+ * If we are coming out of delete mode, but
+ * delete and insert mode end with the same sequence,
+ * it wins to pretend we are now in insert mode,
+ * since we will likely want to be there again soon
+ * if we just moved over to delete space from part of
+ * a tab (above).
+ */
+enddm()
+{
+
+       if (eq(DM, IM)) {
+               insmode = 1;
+               return;
+       }
+       vputp(ED, 0);
+}
+
+/*
+ * In and out of insert mode.
+ * Note that the code here demands that there be
+ * a string for insert mode (the null string) even
+ * if the terminal does all insertions a single character
+ * at a time, since it branches based on whether IM is null.
+ */
+goim()
+{
+
+       if (!insmode)
+               vputp(IM, 0);
+       insmode = 1;
+}
+
+endim()
+{
+
+       if (insmode) {
+               vputp(EI, 0);
+               insmode = 0;
+       }
+}
+
+/*
+ * Put the character c on the screen at the current cursor position.
+ * This routine handles wraparound and scrolling and understands not
+ * to roll when splitw is set, i.e. we are working in the echo area.
+ * There is a bunch of hacking here dealing with the difference between
+ * QUOTE, QUOTE|' ', and ' ' for CONCEPT-100 like terminals, and also
+ * code to deal with terminals which overstrike, including CRT's where
+ * you can erase overstrikes with some work.  CRT's which do underlining
+ * implicitly which has to be erased (like CONCEPTS) are also handled.
+ */
+vputchar(c)
+       register int c;
+{
+       register char *tp;
+       register int d;
+
+       c &= (QUOTE|TRIM);
+#ifdef TRACE
+       if (trace)
+               tracec(c);
+#endif
+       /* Patch to fix problem of >79 chars on echo line: don't echo extras */
+       if (destcol >= WCOLS-1 && splitw && destline == WECHO)
+               return;
+       if (destcol >= WCOLS) {
+               destline += destcol / WCOLS;
+               destcol %= WCOLS;
+       }
+       if (destline > WBOT && (!splitw || destline > WECHO))
+               vrollup(destline);
+       tp = vtube[destline] + destcol;
+       switch (c) {
+
+       case '\t':
+               vgotab();
+               return;
+
+       case ' ':
+               /*
+                * We can get away without printing a space in a number
+                * of cases, but not always.  We get away with doing nothing
+                * if we are not in insert mode, and not on a CONCEPT-100
+                * like terminal, and either not in hardcopy open or in hardcopy
+                * open on a terminal with no overstriking, provided,
+                * in all cases, that nothing has ever been displayed
+                * at this position.  Ugh.
+                */
+               if (!insmode && !IN && (state != HARDOPEN || OS) && (*tp&TRIM) == 0) {
+                       *tp = ' ';
+                       destcol++;
+                       return;
+               }
+               goto def;
+
+       case QUOTE:
+               if (insmode) {
+                       /*
+                        * When in insert mode, tabs have to expand
+                        * to real, printed blanks.
+                        */
+                       c = ' ' | QUOTE;
+                       goto def;
+               }
+               if (*tp == 0) {
+                       /*
+                        * A ``space''.
+                        */
+                       if ((hold & HOLDPUPD) == 0)
+                               *tp = QUOTE;
+                       destcol++;
+                       return;
+               }
+               /*
+                * A ``space'' ontop of a part of a tab.
+                */
+               if (*tp & QUOTE) {
+                       destcol++;
+                       return;
+               }
+               c = ' ' | QUOTE;
+               /* fall into ... */
+
+def:
+       default:
+               d = *tp & TRIM;
+               /*
+                * Now get away with doing nothing if the characters
+                * are the same, provided we are not in insert mode
+                * and if we are in hardopen, that the terminal has overstrike.
+                */
+               if (d == (c & TRIM) && !insmode && (state != HARDOPEN || OS)) {
+                       if ((hold & HOLDPUPD) == 0)
+                               *tp = c;
+                       destcol++;
+                       return;
+               }
+               /*
+                * Backwards looking optimization.
+                * The low level cursor motion routines will use
+                * a cursor motion right sequence to step 1 character
+                * right.  On, e.g., a DM3025A this is 2 characters
+                * and printing is noticeably slower at 300 baud.
+                * Since the low level routines are not allowed to use
+                * spaces for positioning, we discover the common
+                * case of a single space here and force a space
+                * to be printed.
+                */
+               if (destcol == outcol + 1 && tp[-1] == ' ' && outline == destline) {
+                       vputc(' ');
+                       outcol++;
+               }
+
+               /*
+                * This is an inline expansion a call to vcsync() dictated
+                * by high frequency in a profile.
+                */
+               if (outcol != destcol || outline != destline)
+                       vgoto(destline, destcol);
+
+               /*
+                * Deal with terminals which have overstrike.
+                * We handle erasing general overstrikes, erasing
+                * underlines on terminals (such as CONCEPTS) which
+                * do underlining correctly automatically (e.g. on nroff
+                * output), and remembering, in hardcopy mode,
+                * that we have overstruct something.
+                */
+               if (!insmode && d && d != ' ' && d != (c & TRIM)) {
+                       if (EO && (OS || UL && (c == '_' || d == '_'))) {
+                               vputc(' ');
+                               outcol++, destcol++;
+                               back1();
+                       } else
+                               rubble = 1;
+               }
+
+               /*
+                * Unless we are just bashing characters around for
+                * inner working of insert mode, update the display.
+                */
+               if ((hold & HOLDPUPD) == 0)
+                       *tp = c;
+
+               /*
+                * In insert mode, put out the IC sequence, padded
+                * based on the depth of the current line.
+                * A terminal which had no real insert mode, rather
+                * opening a character position at a time could do this.
+                * Actually should use depth to end of current line
+                * but this rarely matters.
+                */
+               if (insmode)
+                       vputp(IC, DEPTH(vcline));
+               vputc(c & TRIM);
+
+               /*
+                * In insert mode, IP is a post insert pad.
+                */
+               if (insmode)
+                       vputp(IP, DEPTH(vcline));
+               destcol++, outcol++;
+
+               /*
+                * CONCEPT braindamage in early models:  after a wraparound
+                * the next newline is eaten.  It's hungry so we just
+                * feed it now rather than worrying about it.
+                */
+               if (XN && outcol % WCOLS == 0)
+                       vputc('\n');
+       }
+}
+
+/*
+ * Delete display positions stcol through endcol.
+ * Amount of use of special terminal features here is limited.
+ */
+physdc(stcol, endcol)
+       int stcol, endcol;
+{
+       register char *tp, *up;
+       char *tpe;
+       register int i;
+       register int nc = endcol - stcol;
+
+#ifdef IDEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "physdc(%d, %d)\n", stcol, endcol);
+#endif
+       if (!DC || nc <= 0)
+               return;
+       if (IN) {
+               /*
+                * CONCEPT-100 like terminal.
+                * If there are any ``spaces'' in the material to be
+                * deleted, then this is too hard, just retype.
+                */
+               vprepins();
+               up = vtube0 + stcol;
+               i = nc;
+               do
+                       if ((*up++ & (QUOTE|TRIM)) == QUOTE)
+                               return;
+               while (--i);
+               i = 2 * nc;
+               do
+                       if (*up == 0 || (*up++ & QUOTE) == QUOTE)
+                               return;
+               while (--i);
+               vgotoCL(stcol);
+       } else {
+               /*
+                * HP like delete mode.
+                * Compute how much text we are moving over by deleting.
+                * If it appears to be faster to just retype
+                * the line, do nothing and that will be done later.
+                * We are assuming 2 output characters per deleted
+                * characters and that clear to end of line is available.
+                */
+               i = stcol / WCOLS;
+               if (i != endcol / WCOLS)
+                       return;
+               i += LINE(vcline);
+               stcol %= WCOLS;
+               endcol %= WCOLS;
+               up = vtube[i]; tp = up + endcol; tpe = up + WCOLS;
+               while (tp < tpe && *tp)
+                       tp++;
+               if (tp - (up + stcol) < 2 * nc)
+                       return;
+               vgoto(i, stcol);
+       }
+
+       /*
+        * Go into delete mode and do the actual delete.
+        * Padding is on DC itself.
+        */
+       godm();
+       for (i = nc; i > 0; i--)
+               vputp(DC, DEPTH(vcline));
+       vputp(ED, 0);
+
+       /*
+        * Straighten up.
+        * With CONCEPT like terminals, characters are pulled left
+        * from first following null.  HP like terminals shift rest of
+        * this (single physical) line rigidly.
+        */
+       if (IN) {
+               up = vtube0 + stcol;
+               tp = vtube0 + endcol;
+               while (i = *tp++) {
+                       if ((i & (QUOTE|TRIM)) == QUOTE)
+                               break;
+                       *up++ = i;
+               }
+               do
+                       *up++ = i;
+               while (--nc);
+       } else {
+               copy(up + stcol, up + endcol, WCOLS - endcol);
+               vclrbyte(tpe - nc, nc);
+       }
+}
+
+#ifdef TRACE
+tfixnl()
+{
+
+       if (trubble || techoin)
+               fprintf(trace, "\n");
+       trubble = 0, techoin = 0;
+}
+
+tvliny()
+{
+       register int i;
+
+       if (!trace)
+               return;
+       tfixnl();
+       fprintf(trace, "vcnt = %d, vcline = %d, vliny = ", vcnt, vcline);
+       for (i = 0; i <= vcnt; i++) {
+               fprintf(trace, "%d", LINE(i));
+               if (FLAGS(i) & VDIRT)
+                       fprintf(trace, "*");
+               if (DEPTH(i) != 1)
+                       fprintf(trace, "<%d>", DEPTH(i));
+               if (i < vcnt)
+                       fprintf(trace, " ");
+       }
+       fprintf(trace, "\n");
+}
+
+tracec(c)
+       char c;
+{
+
+       if (!techoin)
+               trubble = 1;
+       if (c == ESCAPE)
+               fprintf(trace, "$");
+       else if (c < ' ' || c == DELETE)
+               fprintf(trace, "^%c", ctlof(c));
+       else
+               fprintf(trace, "%c", c);
+}
+#endif
+
+/*
+ * Put a character with possible tracing.
+ */
+vputch(c)
+       int c;
+{
+
+#ifdef TRACE
+       if (trace)
+               tracec(c);
+#endif
+       vputc(c);
+}
diff --git a/usr/src/cmd/ex/ex_vwind.c b/usr/src/cmd/ex/ex_vwind.c
new file mode 100644 (file)
index 0000000..18f1104
--- /dev/null
@@ -0,0 +1,460 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include "ex.h"
+#include "ex_tty.h"
+#include "ex_vis.h"
+
+/*
+ * Routines to adjust the window, showing specified lines
+ * in certain positions on the screen, and scrolling in both
+ * directions.  Code here is very dependent on mode (open versus visual).
+ */
+
+/*
+ * Move in a nonlocal way to line addr.
+ * If it isn't on screen put it in specified context.
+ * New position for cursor is curs.
+ * Like most routines here, we vsave().
+ */
+vmoveto(addr, curs, context)
+       register line *addr;
+       char *curs;
+       char context;
+{
+
+       markit(addr);
+       vsave();
+       vjumpto(addr, curs, context);
+}
+
+/*
+ * Vjumpto is like vmoveto, but doesn't mark previous
+ * context or save linebuf as current line.
+ */
+vjumpto(addr, curs, context)
+       register line *addr;
+       char *curs;
+       char context;
+{
+
+       noteit(0);
+       if (context != 0)
+               vcontext(addr, context);
+       else
+               vshow(addr, NOLINE);
+       noteit(1);
+       vnline(curs);
+}
+
+/*
+ * Go up or down cnt (negative is up) to new position curs.
+ */
+vupdown(cnt, curs)
+       register int cnt;
+       char *curs;
+{
+
+       if (cnt > 0)
+               vdown(cnt, 0, 0);
+       else if (cnt < 0)
+               vup(-cnt, 0, 0);
+       if (vcnt == 0)
+               vrepaint(curs);
+       else
+               vnline(curs);
+}
+
+/*
+ * Go up cnt lines, afterwards preferring to be ind
+ * logical lines from the top of the screen.
+ * If scroll, then we MUST use a scroll.
+ * Otherwise clear and redraw if motion is far.
+ */
+vup(cnt, ind, scroll)
+       register int cnt, ind;
+       bool scroll;
+{
+       register int i, tot;
+
+       if (dot == one) {
+               beep();
+               return;
+       }
+       vsave();
+       i = lineDOT() - 1;
+       if (cnt > i) {
+               ind -= cnt - i;
+               if (ind < 0)
+                       ind = 0;
+               cnt = i;
+       }
+       if (!scroll && cnt <= vcline) {
+               vshow(dot - cnt, NOLINE);
+               return;
+       }
+       cnt -= vcline, dot -= vcline, vcline = 0;
+       if (hold & HOLDWIG)
+               goto contxt;
+       if (state == VISUAL && !AL && !SR &&
+           cnt <= WTOP - ZERO && vfit(dot - cnt, cnt) <= WTOP - ZERO)
+               goto okr;
+       tot = WECHO - ZERO;
+       if (state != VISUAL || (!AL && !SR) || (!scroll && (cnt > tot || vfit(dot - cnt, cnt) > tot / 3 + 1))) {
+               if (ind > basWLINES / 2)
+                       ind = basWLINES / 3;
+contxt:
+               vcontext(dot + ind - cnt, '.');
+               return;
+       }
+okr:
+       vrollR(cnt);
+       if (scroll) {
+               vcline += ind, dot += ind;
+               if (vcline >= vcnt)
+                       dot -= vcline - vcnt + 1, vcline = vcnt - 1;
+               getDOT();
+       }
+}
+
+/*
+ * Like vup, but scrolling down.
+ */
+vdown(cnt, ind, scroll)
+       register int cnt, ind;
+       bool scroll;
+{
+       register int i, tot;
+
+       if (dot == dol) {
+               beep();
+               return;
+       }
+       vsave();
+       i = dol - dot;
+       if (cnt > i) {
+               ind -= cnt - i;
+               if (ind < 0)
+                       ind = 0;
+               cnt = i;
+       }
+       i = vcnt - vcline - 1;
+       if (!scroll && cnt <= i) {
+               vshow(dot + cnt, NOLINE);
+               return;
+       }
+       cnt -= i, dot += i, vcline += i;
+       if (hold & HOLDWIG)
+               goto dcontxt;
+       if (!scroll) {
+               tot = WECHO - ZERO;
+               if (state != VISUAL || cnt - tot > 0 || vfit(dot, cnt) > tot / 3 + 1) {
+dcontxt:
+                       vcontext(dot + cnt, '.');
+                       return;
+               }
+       }
+       if (cnt > 0)
+               vroll(cnt);
+       if (state == VISUAL && scroll) {
+               vcline -= ind, dot -= ind;
+               if (vcline < 0)
+                       dot -= vcline, vcline = 0;
+               getDOT();
+       }
+}
+
+/*
+ * Show line addr in context where on the screen.
+ * Work here is in determining new top line implied by
+ * this placement of line addr, since we always draw from the top.
+ */
+vcontext(addr, where)
+       register line *addr;
+       char where;
+{
+       register line *top;
+
+       getline(*addr);
+       if (state != VISUAL)
+               top = addr;
+       else switch (where) {
+
+       case '^':
+               addr = vback(addr, basWLINES - vdepth());
+               getline(*addr);
+               /* fall into ... */
+
+       case '-':
+               top = vback(addr, basWLINES - vdepth());
+               getline(*addr);
+               break;
+
+       case '.':
+               top = vback(addr, basWLINES / 2 - vdepth());
+               getline(*addr);
+               break;
+
+       default:
+               top = addr;
+               break;
+       }
+       if (state == ONEOPEN && LINE(0) == WBOT)
+               vup1();
+       vcnt = vcline = 0;
+       vclean();
+       if (state == CRTOPEN)
+               vup1();
+       vshow(addr, top);
+}
+
+/*
+ * Get a clean line.  If we are in a hard open
+ * we may be able to reuse the line we are on
+ * if it is blank.  This is a real win.
+ */
+vclean()
+{
+
+       if (state != VISUAL && state != CRTOPEN) {
+               destcol = 0;
+               if (!ateopr())
+                       vup1();
+               vcnt = 0;
+       }
+}
+
+/*
+ * Show line addr with the specified top line on the screen.
+ * Top may be 0; in this case have vcontext compute the top
+ * (and call us recursively).  Eventually, we clear the screen
+ * (or its open mode equivalent) and redraw.
+ */
+vshow(addr, top)
+       line *addr, *top;
+{
+#ifndef CBREAK
+       register bool fried = 0;
+#endif
+       register int cnt = addr - dot;
+       register int i = vcline + cnt;
+       short oldhold = hold;
+
+       if (state != HARDOPEN && state != ONEOPEN && i >= 0 && i < vcnt) {
+               dot = addr;
+               getDOT();
+               vcline = i;
+               return;
+       }
+       if (state != VISUAL) {
+               dot = addr;
+               vopen(dot, WBOT);
+               return;
+       }
+       if (top == 0) {
+               vcontext(addr, '.');
+               return;
+       }
+       dot = top;
+#ifndef CBREAK
+       if (vcookit(2))
+               fried++, vcook();
+#endif
+       oldhold = hold;
+       hold |= HOLDAT;
+       vclear();
+       vreset(0);
+       vredraw(WTOP);
+       /* error if vcline >= vcnt ! */
+       vcline = addr - top;
+       dot = addr;
+       getDOT();
+       hold = oldhold;
+       vsync(LASTLINE);
+#ifndef CBREAK
+       if (fried)
+               flusho(), vraw();
+#endif
+}
+
+/*
+ * reset the state.
+ * If inecho then leave us at the beginning of the echo
+ * area;  we are called this way in the middle of a :e escape
+ * from visual, e.g.
+ */
+vreset(inecho)
+       bool inecho;
+{
+
+       vcnt = vcline = 0;
+       WTOP = basWTOP;
+       WLINES = basWLINES;
+       if (inecho)
+               splitw = 1, vgoto(WECHO, 0);
+}
+
+/*
+ * Starting from which line preceding tp uses almost (but not more
+ * than) cnt physical lines?
+ */
+line *
+vback(tp, cnt)
+       register int cnt;
+       register line *tp;
+{
+       register int d;
+
+       if (cnt > 0)
+               for (; tp > one; tp--) {
+                       getline(tp[-1]);
+                       d = vdepth();
+                       if (d > cnt)
+                               break;
+                       cnt -= d;
+               }
+       return (tp);
+}
+
+/*
+ * How much scrolling will it take to roll cnt lines starting at tp?
+ */
+vfit(tp, cnt)
+       register line *tp;
+       int cnt;
+{
+       register int j;
+
+       j = 0;
+       while (cnt > 0) {
+               cnt--;
+               getline(tp[cnt]);
+               j += vdepth();
+       }
+       if (tp > dot)
+               j -= WBOT - LASTLINE;
+       return (j);
+}
+
+/*
+ * Roll cnt lines onto the screen.
+ */
+vroll(cnt)
+       register int cnt;
+{
+#ifndef CBREAK
+       register bool fried = 0;
+#endif
+       short oldhold = hold;
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vroll(%d)\n", cnt);
+#endif
+       if (state != VISUAL)
+               hold |= HOLDAT|HOLDROL;
+       if (WBOT == WECHO) {
+               vcnt = 0;
+               if (state == ONEOPEN)
+                       vup1();
+       }
+#ifndef CBREAK
+       if (vcookit(cnt))
+               fried++, vcook();
+#endif
+       for (; cnt > 0 && Peekkey != ATTN; cnt--) {
+               dot++, vcline++;
+               vopen(dot, LASTLINE);
+               vscrap();
+       }
+       hold = oldhold;
+       if (state == HARDOPEN)
+               sethard();
+       vsyncCL();
+#ifndef CBREAK
+       if (fried)
+               flusho(), vraw();
+#endif
+}
+
+/*
+ * Roll backwards (scroll up).
+ */
+vrollR(cnt)
+       register int cnt;
+{
+       register bool fried = 0;
+       short oldhold = hold;
+
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vrollR(%d), dot=%d\n", cnt, lineDOT());
+#endif
+#ifndef CBREAK
+       if (vcookit(cnt))
+               fried++, vcook();
+#endif
+       if (WBOT == WECHO)
+               vcnt = 0;
+       heldech = 0;
+       hold |= HOLDAT|HOLDECH;
+       for (; cnt > 0 && Peekkey != ATTN; cnt--) {
+               dot--;
+               vopen(dot, WTOP);
+               vscrap();
+       }
+       hold = oldhold;
+       if (heldech)
+               vclrech(0);
+       vsync(LINE(vcnt-1));
+#ifndef CBREAK
+       if (fried)
+               flusho(), vraw();
+#endif
+}
+
+/*
+ * Go into cooked mode (allow interrupts) during
+ * a scroll if we are at less than 1200 baud and not
+ * a 'vi' command, of if we are in a 'vi' command and the
+ * scroll is more than 2 full screens.
+ *
+ * BUG:                An interrupt during a scroll in this way
+ *             dumps to command mode.
+ */
+vcookit(cnt)
+       register int cnt;
+{
+
+       return (cnt > 1 && (ospeed < B1200 && !initev || cnt > LINES * 2));
+}
+
+/*
+ * Determine displayed depth of current line.
+ */
+vdepth()
+{
+       register int d;
+
+       d = (column(NOSTR) + WCOLS - 1 + (Putchar == listchar) + IN) / WCOLS;
+#ifdef ADEBUG
+       if (trace)
+               tfixnl(), fprintf(trace, "vdepth returns %d\n", d == 0 ? 1 : d);
+#endif
+       return (d == 0 ? 1 : d);
+}
+
+/*
+ * Move onto a new line, with cursor at position curs.
+ */
+vnline(curs)
+       char *curs;
+{
+
+       if (curs)
+               wcursor = curs;
+       else if (vmoving)
+               wcursor = vfindcol(vmovcol);
+       else
+               wcursor = vskipwh(linebuf);
+       cursor = linebuf;
+       vmove();
+}
diff --git a/usr/src/cmd/ex/expreserve.c b/usr/src/cmd/ex/expreserve.c
new file mode 100644 (file)
index 0000000..373aa69
--- /dev/null
@@ -0,0 +1,358 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/dir.h>
+#include <pwd.h>
+#include "local/uparm.h"
+
+#ifdef VMUNIX
+#define        HBLKS   2
+#else
+#define        HBLKS   1
+#endif
+
+/*
+ * Expreserve - preserve a file in usrpath(preserve)
+ * Bill Joy UCB November 13, 1977
+ *
+ * This routine is very naive - it doesn't remove anything from
+ * usrpath(preserve)... this may mean that we will be unable to preserve
+ * stuff there... the danger in doing anything with usrpath(preserve)
+ * is that the clock may be screwed up and we may get confused.
+ *
+ * We are called in two ways - first from the editor with no argumentss
+ * and the standard input open on the temp file. Second with an argument
+ * to preserve the entire contents of /tmp (root only).
+ *
+ * BUG: should do something about preserving Rx... (register contents)
+ *      temporaries.
+ */
+
+#ifndef VMUNIX
+#define        LBLKS   125
+#else
+#define        LBLKS   900
+#endif
+#define        FNSIZE  128
+
+struct         header {
+       time_t  Time;                   /* Time temp file last updated */
+       short   Uid;                    /* This users identity */
+#ifndef VMUNIX
+       short   Flines;                 /* Number of lines in file */
+#else
+       int     Flines;
+#endif
+       char    Savedfile[FNSIZE];      /* The current file name */
+       short   Blocks[LBLKS];          /* Blocks where line pointers stashed */
+} H;
+
+#ifdef lint
+#define        ignore(a)       Ignore(a)
+#define        ignorl(a)       Ignorl(a)
+#else
+#define        ignore(a)       a
+#define        ignorl(a)       a
+#endif
+
+struct passwd *getpwuid();
+off_t  lseek();
+FILE   *popen();
+
+#define eq(a, b) strcmp(a, b) == 0
+
+main(argc)
+       int argc;
+{
+       register FILE *tf;
+       struct direct dirent;
+       struct stat stbuf;
+
+       /*
+        * If only one argument, then preserve the standard input.
+        */
+       if (argc == 1) {
+               if (copyout((char *) 0))
+                       exit(1);
+               exit(0);
+       }
+
+       /*
+        * If not super user, then can only preserve standard input.
+        */
+       if (getuid()) {
+               fprintf(stderr, "NOT super user\n");
+               exit(1);
+       }
+
+       /*
+        * ... else preserve all the stuff in /tmp, removing
+        * it as we go.
+        */
+       if (chdir("/tmp") < 0) {
+               perror("/tmp");
+               exit(1);
+       }
+
+       tf = fopen(".", "r");
+       if (tf == NULL) {
+               perror("/tmp");
+               exit(1);
+       }
+       while (fread((char *) &dirent, sizeof dirent, 1, tf) == 1) {
+               if (dirent.d_ino == 0)
+                       continue;
+               /*
+                * Ex temporaries must begin with Ex;
+                * we check that the 10th character of the name is null
+                * so we won't have to worry about non-null terminated names
+                * later on.
+                */
+               if (dirent.d_name[0] != 'E' || dirent.d_name[1] != 'x' || dirent.d_name[10])
+                       continue;
+               if (stat(dirent.d_name, &stbuf))
+                       continue;
+               if ((stbuf.st_mode & S_IFMT) != S_IFREG)
+                       continue;
+               /*
+                * Save the bastard.
+                */
+               ignore(copyout(dirent.d_name));
+       }
+       exit(0);
+}
+
+char   pattern[] =     usrpath(preserve/Exaa`XXXXX);
+
+/*
+ * Copy file name into usrpath(preserve)/...
+ * If name is (char *) 0, then do the standard input.
+ * We make some checks on the input to make sure it is
+ * really an editor temporary, generate a name for the
+ * file (this is the slowest thing since we must stat
+ * to find a unique name), and finally copy the file.
+ */
+copyout(name)
+       char *name;
+{
+       int i;
+       static int reenter;
+       char buf[BUFSIZ];
+
+       /*
+        * The first time we put in the digits of our
+        * process number at the end of the pattern.
+        */
+       if (reenter == 0) {
+               mkdigits(pattern);
+               reenter++;
+       }
+
+       /*
+        * If a file name was given, make it the standard
+        * input if possible.
+        */
+       if (name != 0) {
+               ignore(close(0));
+               /*
+                * Need read/write access for arcane reasons
+                * (see below).
+                */
+               if (open(name, 2) < 0)
+                       return (-1);
+       }
+
+       /*
+        * Get the header block.
+        */
+       ignorl(lseek(0, 0l, 0));
+       if (read(0, (char *) &H, sizeof H) != sizeof H) {
+format:
+               if (name == 0)
+                       fprintf(stderr, "Buffer format error\n");
+               return (-1);
+       }
+
+       /*
+        * Consistency checsks so we don't copy out garbage.
+        */
+       if (H.Flines < 0) {
+#ifdef DEBUG
+               fprintf(stderr, "Negative number of lines\n");
+#endif
+               goto format;
+       }
+       if (H.Blocks[0] != HBLKS || H.Blocks[1] != HBLKS+1) {
+#ifdef DEBUG
+               fprintf(stderr, "Blocks %d %d\n", H.Blocks[0], H.Blocks[1]);
+#endif
+               goto format;
+       }
+       if (name == 0 && H.Uid != getuid()) {
+#ifdef DEBUG
+               fprintf(stderr, "Wrong user-id\n");
+#endif
+               goto format;
+       }
+       if (lseek(0, 0l, 0)) {
+#ifdef DEBUG
+               fprintf(stderr, "Negative number of lines\n");
+#endif
+               goto format;
+       }
+
+       /*
+        * If no name was assigned to the file, then give it the name
+        * LOST, by putting this in the header.
+        */
+       if (H.Savedfile[0] == 0) {
+               strcpy(H.Savedfile, "LOST");
+               ignore(write(0, (char *) &H, sizeof H));
+               H.Savedfile[0] = 0;
+               lseek(0, 0l, 0);
+       }
+
+       /*
+        * File is good.  Get a name and create a file for the copy.
+        */
+       mknext(pattern);
+       ignore(close(1));
+       if (creat(pattern, 0600) < 0) {
+               if (name == 0)
+                       perror(pattern);
+               return (1);
+       }
+
+       /*
+        * Make the target be owned by the owner of the file.
+        */
+       ignore(chown(pattern, H.Uid, 0));
+
+       /*
+        * Copy the file.
+        */
+       for (;;) {
+               i = read(0, buf, BUFSIZ);
+               if (i < 0) {
+                       if (name)
+                               perror("Buffer read error");
+                       ignore(unlink(pattern));
+                       return (-1);
+               }
+               if (i == 0) {
+                       if (name)
+                               ignore(unlink(name));
+                       notify(H.Uid, H.Savedfile, (int) name);
+                       return (0);
+               }
+               if (write(1, buf, i) != i) {
+                       if (name == 0)
+                               perror(pattern);
+                       unlink(pattern);
+                       return (-1);
+               }
+       }
+}
+
+/*
+ * Blast the last 5 characters of cp to be the process number.
+ */
+mkdigits(cp)
+       char *cp;
+{
+       register int i, j;
+
+       for (i = getpid(), j = 5, cp += strlen(cp); j > 0; i /= 10, j--)
+               *--cp = i % 10 | '0';
+}
+
+/*
+ * Make the name in cp be unique by clobbering up to
+ * three alphabetic characters into a sequence of the form 'aab', 'aac', etc.
+ * Mktemp gets weird names too quickly to be useful here.
+ */
+mknext(cp)
+       char *cp;
+{
+       char *dcp;
+       struct stat stb;
+
+       dcp = cp + strlen(cp) - 1;
+       while (isdigit(*dcp))
+               dcp--;
+whoops:
+       if (dcp[0] == 'z') {
+               dcp[0] = 'a';
+               if (dcp[-1] == 'z') {
+                       dcp[-1] = 'a';
+                       if (dcp[-2] == 'z')
+                               fprintf(stderr, "Can't find a name\n");
+                       dcp[-2]++;
+               } else
+                       dcp[-1]++;
+       } else
+               dcp[0]++;
+       if (stat(cp, &stb) == 0)
+               goto whoops;
+}
+
+/*
+ * Notify user uid that his file fname has been saved.
+ */
+notify(uid, fname, flag)
+       int uid;
+       char *fname;
+{
+       struct passwd *pp = getpwuid(uid);
+       register FILE *mf;
+       char cmd[BUFSIZ];
+
+       if (pp == NULL)
+               return;
+       sprintf(cmd, "mail %s", pp->pw_name);
+       mf = popen(cmd, "w");
+       if (mf == NULL)
+               return;
+       setbuf(mf, cmd);
+       if (fname[0] == 0) {
+               fprintf(mf,
+"A copy of an editor buffer of yours was saved when %s.\n",
+               flag ? "the system went down" : "your phone was hung up");
+               fprintf(mf,
+"No name was associated with this buffer so it has been named \"LOST\".\n");
+       } else
+               fprintf(mf,
+"A copy of an editor buffer of your file \"%s\"\nwas saved when %s.\n", fname,
+               flag ? "the system went down" : "your phone was hung up");
+       fprintf(mf,
+"This buffer can be retrieved using the \"recover\" command of the editor.\n");
+       fprintf(mf,
+"An easy way to do this is to give the command \"ex -r %s\".\n",fname);
+       fprintf(mf,
+"This works for \"edit\" and \"vi\" also.\n");
+       pclose(mf);
+}
+
+/*
+ *     people making love
+ *     never exactly the same
+ *     just like a snowflake 
+ */
+
+#ifdef lint
+Ignore(a)
+       int a;
+{
+
+       a = a;
+}
+
+Ignorl(a)
+       long a;
+{
+
+       a = a;
+}
+#endif
diff --git a/usr/src/cmd/ex/makefile b/usr/src/cmd/ex/makefile
new file mode 100644 (file)
index 0000000..c62b9d7
--- /dev/null
@@ -0,0 +1,136 @@
+VERSION=3.2
+#
+# Ex skeletal makefile for version 7
+#
+# NB: This makefile doesn't indicate any dependencies on header files.
+#
+# Ex is very large - it may not fit on PDP-11's depending on the operating
+# system and the cflags you turn on. Things that can be turned off to save
+# space include LISPCODE (-l flag, showmatch and lisp options), UCVISUAL
+# (visual \ nonsense on upper case only terminals), CHDIR (the undocumented
+# chdir command.)
+#
+# Don't define VFORK unless your system has the VFORK system call,
+# which is like fork but the two processes have only one data space until the
+# child execs. This speeds up ex by saving the memory copy.
+# -DVMUNIX makes an ex which can edit very large files (eg the w2a dictionary)
+# this allows 200000 lines and about 16M byte temp files.
+#
+# If your system expands tabs to 4 spaces you should -DTABS=4 below
+#
+# Ex is likely to overflow the symbol table in your C compiler.
+# It can use -t0 which is (purportedly) a C compiler with a larger
+# symbol table.  The -t1 flag to the C compiler is for a C compiler
+# which puts switch code in I space, increasing the text space size
+# to the benefit of per-user data space.  If you don't have this it
+# doesn't matter much. Another method, which works on v7 pdp-11's,
+# is to use pcc for ex_io.c instead of cc.
+#
+BINDIR=        /usr/ucb
+NBINDIR=/usr/new
+LIBDIR=        /usr/lib
+FOLD=  ${BINDIR}/fold
+CTAGS= ${BINDIR}/ctags
+XSTR=  ${BINDIR}/xstr
+DEBUGFLAGS=    -DTRACE
+NONDEBUGFLAGS= -O
+CFLAGS=        -DTABS=8 -DLISPCODE -DCHDIR -DUCVISUAL -DMACROS -DVFORK -DVMUNIX ${NONDEBUGFLAGS}
+TERMLIB=       -ltermlib
+MKSTR= ${BINDIR}/mkstr
+CXREF= ${BINDIR}/cxref
+INCLUDE=/usr/include
+PR=    pr
+OBJS=  ex.o ex_addr.o ex_cmds.o ex_cmds2.o ex_cmdsub.o ex_data.o ex_get.o \
+       ex_io.o ex_put.o ex_re.o ex_set.o ex_subr.o ex_temp.o ex_tty.o \
+       ex_v.o ex_vadj.o ex_vget.o ex_vmain.o ex_voperate.o \
+       ex_vops.o ex_vops2.o ex_vops3.o ex_vput.o ex_vwind.o \
+       printf.o strings.o
+
+all:   a.out exrecover expreserve tags
+
+.c.o:
+#      ${MKSTR} - ex${VERSION}strings x $*.c
+       ${CC} -E ${CFLAGS} $*.c | ${XSTR} -c -
+#      rm -f x$*.c
+       ${CC} ${CFLAGS} -c x.c 
+       mv x.o $*.o
+
+a.out: ${OBJS}
+       cc -i ${OBJS} ${TERMLIB}
+
+tags:
+       ${CTAGS} -w *.h *.c
+
+${OBJS}: ex_vars.h
+
+ex_vars.h:
+       csh makeoptions ${CFLAGS}
+
+strings.o: strings
+       ${XSTR}
+       ${CC} -c -S xs.c
+       ed - <:rofix xs.s
+       as -o strings.o xs.s
+       rm xs.s
+       
+exrecover: exrecover.o
+       ${CC} ${CFLAGS} exrecover.o -o exrecover
+
+exrecover.o: exrecover.c
+       ${CC} ${CFLAGS} -c -O exrecover.c
+
+expreserve: expreserve.o
+       ${CC} expreserve.o -o expreserve
+
+expreserve.o:
+       ${CC} ${CFLAGS} -c -O expreserve.c
+
+clean:
+#      If we dont have ex we cant make it so dont rm ex_vars.h
+       -rm -f a.out exrecover expreserve ex${VERSION}strings strings core trace tags
+       -rm -f *.o x*.[cs]
+
+ninstall: a.out
+       -rm -f ${NBINDIR}/ex ${NBINDIR}/vi
+       cp a.out ${NBINDIR}/ex
+#      -cp ex${VERSION}strings ${LIBDIR}/ex${VERSION}strings
+       ln ${NBINDIR}/ex ${NBINDIR}/vi
+       chmod 1755 ${NBINDIR}/ex
+
+install: a.out exrecover expreserve
+       -rm -f ${DESTDIR}${BINDIR}/ex
+       -rm -f ${DESTDIR}${BINDIR}/vi
+       -rm -f ${DESTDIR}${BINDIR}/edit
+       -rm -f ${DESTDIR}${BINDIR}/e
+       -rm -f ${DESTDIR}/usr/bin/ex
+       cp a.out ${DESTDIR}${BINDIR}/ex
+#      cp ex${VERSION}strings ${DESTDIR}/${LIBDIR}/ex${VERSION}strings
+       ln ${DESTDIR}${BINDIR}/ex ${DESTDIR}${BINDIR}/edit
+       ln ${DESTDIR}${BINDIR}/ex ${DESTDIR}${BINDIR}/e
+       ln ${DESTDIR}${BINDIR}/ex ${DESTDIR}${BINDIR}/vi
+       ln ${DESTDIR}${BINDIR}/ex ${DESTDIR}/usr/bin/ex
+       chmod 1755 ${DESTDIR}${BINDIR}/ex
+       cp exrecover ${DESTDIR}${LIBDIR}/ex${VERSION}recover
+       cp expreserve ${DESTDIR}/${LIBDIR}/ex${VERSION}preserve
+       chmod 4755 ${DESTDIR}${LIBDIR}/ex${VERSION}recover ${DESTDIR}${LIBDIR}/ex${VERSION}preserve
+       mkdir ${DESTDIR}/usr/preserve
+
+lint:
+       lint ex.c ex_?*.c
+       lint -u exrecover.c
+       lint expreserve.c
+
+print:
+       @${PR} READ* BUGS
+       @${PR} makefile*
+       @${PR} /etc/termcap
+       @(size -l a.out ; size *.o) | ${PR} -h sizes
+       @${PR} -h errno.h ${INCLUDE}/errno.h
+       @${PR} -h setjmp.h ${INCLUDE}/setjmp.h
+       @${PR} -h sgtty.h ${INCLUDE}/sgtty.h
+       @${PR} -h signal.h ${INCLUDE}/signal.h
+       @${PR} -h sys/stat.h ${INCLUDE}/sys/stat.h
+       @${PR} -h sys/types.h ${INCLUDE}/sys/types.h
+       @ls -ls | ${PR}
+       @${CXREF} *.c | ${PR} -h XREF
+       @${PR} *.h *.c
diff --git a/usr/src/cmd/ex/makeoptions b/usr/src/cmd/ex/makeoptions
new file mode 100755 (executable)
index 0000000..84d2639
--- /dev/null
@@ -0,0 +1,40 @@
+#
+# remake options -- this isn't necessary unless you add/delete options
+#
+       onintr ifintr
+       cp ex_data.c /tmp/$$.c
+       ex - /tmp/$$.c <<'%'
+               g/^#include/d
+               w
+               q
+'%'
+       cc -E $* /tmp/$$.c >/tmp/foo.c
+       ex - /tmp/foo.c <<'X'
+               g/^# /d
+               set sh=/bin/csh
+               g/^[    ]*$/d
+               1,/options/d
+               /}/-1,$d
+               1,$s/   "//
+               1,$s/".*//
+               1m$
+               w! ex_vars.h
+               !rm -f %; num ex_vars.h >%
+               e
+               $t0
+               1s/......../     0  /
+               1,$s/\(......\)\(.*\)/#define   \U\2\L  \1/
+               1,$s/    */     /g
+               g/  */s//       /g
+               w
+               !rm -f ex_vars.h; expand -8,24 % >ex_vars.h
+               e! ex_vars.h
+               $i
+
+.
+               $s/e[   ].*[    ]/e     NOPTS   /
+               w
+               q
+'X'
+ifintr:
+       rm /tmp/foo.c
diff --git a/usr/src/cmd/ex/popen.c b/usr/src/cmd/ex/popen.c
new file mode 100644 (file)
index 0000000..26d89eb
--- /dev/null
@@ -0,0 +1,42 @@
+/* Copyright (c) 1979 Regents of the University of California */
+#include       "stdio.h"
+#define tst(a,b) (*mode == 'r' ? (b) : (a))
+
+FILE *
+popen(cmd,mode)
+char *cmd;
+char *mode;
+{
+       register i;
+       FILE *fptr;
+       struct pstruct {
+               int reader;
+               int writer;
+       } str;
+
+       if (pipe(&str)<0) return NULL;
+       if ((i=fork())==0) {
+               close(tst(str.writer,str.reader));
+               close(tst(0,1));
+               dup(tst(str.reader,str.writer));
+               close(tst(str.reader,str.writer));
+               execl("/bin/sh","sh","-c",cmd,0);
+               exit(1);
+       }
+       if (i== -1) return NULL;
+       close(tst(str.reader,str.writer));
+       fptr=fopen("/dev/null",tst("w","r"));
+       setbuf(fptr,NULL);
+       fptr->_file=tst(str.writer,str.reader);
+       return fptr;
+}
+
+pclose(ptr)
+FILE *ptr;
+{
+       int st;
+
+       fclose(ptr);
+       wait(&st);
+       return st;
+}
diff --git a/usr/src/cmd/ex/printf.c b/usr/src/cmd/ex/printf.c
new file mode 100644 (file)
index 0000000..be24ebe
--- /dev/null
@@ -0,0 +1,340 @@
+/* char printf_id[] = "@(#) printf.c:2.2 6/5/79";*/
+#include "varargs.h"
+/*
+ * This version of printf is compatible with the Version 7 C
+ * printf. The differences are only minor except that this
+ * printf assumes it is to print through putchar. Version 7
+ * printf is more general (and is much larger) and includes
+ * provisions for floating point.
+ */
+
+#define MAXOCT 11      /* Maximum octal digits in a long */
+#define MAXINT 32767   /* largest normal length positive integer */
+#define BIG    1000000000  /* largest power of 10 less than an unsigned long */
+#define MAXDIGS        10      /* number of digits in BIG */
+
+static int width, sign, fill;
+
+char *_p_dconv();
+
+printf(va_alist)
+       va_dcl
+{
+       va_list ap;
+       register char *fmt;
+       char fcode;
+       int prec;
+       int length,mask1,nbits,n;
+       long int mask2, num;
+       register char *bptr;
+       char *ptr;
+       char buf[134];
+
+       va_start(ap);
+       fmt = va_arg(ap,char *);
+       for (;;) {
+               /* process format string first */
+               while ((fcode = *fmt++)!='%') {
+                       /* ordinary (non-%) character */
+                       if (fcode=='\0')
+                               return;
+                       putchar(fcode);
+               }
+               /* length modifier: -1 for h, 1 for l, 0 for none */
+               length = 0;
+               /* check for a leading - sign */
+               sign = 0;
+               if (*fmt == '-') {
+                       sign++;
+                       fmt++;
+               }
+               /* a '0' may follow the - sign */
+               /* this is the requested fill character */
+               fill = 1;
+               if (*fmt == '0') {
+                       fill--;
+                       fmt++;
+               }
+               
+               /* Now comes a digit string which may be a '*' */
+               if (*fmt == '*') {
+                       width = va_arg(ap, int);
+                       if (width < 0) {
+                               width = -width;
+                               sign = !sign;
+                       }
+                       fmt++;
+               }
+               else {
+                       width = 0;
+                       while (*fmt>='0' && *fmt<='9')
+                               width = width * 10 + (*fmt++ - '0');
+               }
+               
+               /* maybe a decimal point followed by more digits (or '*') */
+               if (*fmt=='.') {
+                       if (*++fmt == '*') {
+                               prec = va_arg(ap, int);
+                               fmt++;
+                       }
+                       else {
+                               prec = 0;
+                               while (*fmt>='0' && *fmt<='9')
+                                       prec = prec * 10 + (*fmt++ - '0');
+                       }
+               }
+               else
+                       prec = -1;
+               
+               /*
+                * At this point, "sign" is nonzero if there was
+                * a sign, "fill" is 0 if there was a leading
+                * zero and 1 otherwise, "width" and "prec"
+                * contain numbers corresponding to the digit
+                * strings before and after the decimal point,
+                * respectively, and "fmt" addresses the next
+                * character after the whole mess. If there was
+                * no decimal point, "prec" will be -1.
+                */
+               switch (*fmt) {
+                       case 'L':
+                       case 'l':
+                               length = 2;
+                               /* no break!! */
+                       case 'h':
+                       case 'H':
+                               length--;
+                               fmt++;
+                               break;
+               }
+               
+               /*
+                * At exit from the following switch, we will
+                * emit the characters starting at "bptr" and
+                * ending at "ptr"-1, unless fcode is '\0'.
+                */
+               switch (fcode = *fmt++) {
+                       /* process characters and strings first */
+                       case 'c':
+                               buf[0] = va_arg(ap, int);
+                               ptr = bptr = &buf[0];
+                               if (buf[0] != '\0')
+                                       ptr++;
+                               break;
+                       case 's':
+                               bptr = va_arg(ap,char *);
+                               if (bptr==0)
+                                       bptr = "(null pointer)";
+                               if (prec < 0)
+                                       prec = MAXINT;
+                               for (n=0; *bptr++ && n < prec; n++) ;
+                               ptr = --bptr;
+                               bptr -= n;
+                               break;
+                       case 'O':
+                               length = 1;
+                               fcode = 'o';
+                               /* no break */
+                       case 'o':
+                       case 'X':
+                       case 'x':
+                               if (length > 0)
+                                       num = va_arg(ap,long);
+                               else
+                                       num = (unsigned)va_arg(ap,int);
+                               if (fcode=='o') {
+                                       mask1 = 0x7;
+                                       mask2 = 0x1fffffffL;
+                                       nbits = 3;
+                               }
+                               else {
+                                       mask1 = 0xf;
+                                       mask2 = 0x0fffffffL;
+                                       nbits = 4;
+                               }
+                               n = (num!=0);
+                               bptr = buf + MAXOCT + 3;
+                               /* shift and mask for speed */
+                               do
+                                   if (((int) num & mask1) < 10)
+                                       *--bptr = ((int) num & mask1) + 060;
+                                   else
+                                       *--bptr = ((int) num & mask1) + 0127;
+                               while (num = (num >> nbits) & mask2);
+                               
+                               if (fcode=='o') {
+                                       if (n)
+                                               *--bptr = '0';
+                               }
+                               else
+                                       if (!sign && fill <= 0) {
+                                               putchar('0');
+                                               putchar(fcode);
+                                               width -= 2;
+                                       }
+                                       else {
+                                               *--bptr = fcode;
+                                               *--bptr = '0';
+                                       }
+                               ptr = buf + MAXOCT + 3;
+                               break;
+                       case 'D':
+                       case 'U':
+                       case 'I':
+                               length = 1;
+                               fcode = fcode + 'a' - 'A';
+                               /* no break */
+                       case 'd':
+                       case 'i':
+                       case 'u':
+                               if (length > 0)
+                                       num = va_arg(ap,long);
+                               else {
+                                       n = va_arg(ap,int);
+                                       if (fcode=='u')
+                                               num = (unsigned) n;
+                                       else
+                                               num = (long) n;
+                               }
+                               if (n = (fcode != 'u' && num < 0))
+                                       num = -num;
+                               /* now convert to digits */
+                               bptr = _p_dconv(num, buf);
+                               if (n)
+                                       *--bptr = '-';
+                               if (fill == 0)
+                                       fill = -1;
+                               ptr = buf + MAXDIGS + 1;
+                               break;
+                       default:
+                               /* not a control character, 
+                                * print it.
+                                */
+                               ptr = bptr = &fcode;
+                               ptr++;
+                               break;
+                       }
+                       if (fcode != '\0')
+                               _p_emit(bptr,ptr);
+       }
+       va_end(ap);
+}
+
+/* _p_dconv converts the unsigned long integer "value" to
+ * printable decimal and places it in "buffer", right-justified.
+ * The value returned is the address of the first non-zero character,
+ * or the address of the last character if all are zero.
+ * The result is NOT null terminated, and is MAXDIGS characters long,
+ * starting at buffer[1] (to allow for insertion of a sign).
+ *
+ * This program assumes it is running on 2's complement machine
+ * with reasonable overflow treatment.
+ */
+char *
+_p_dconv(value, buffer)
+       long value;
+       char *buffer;
+{
+       register char *bp;
+       register int svalue;
+       int n;
+       long lval;
+       
+       bp = buffer;
+       
+       /* zero is a special case */
+       if (value == 0) {
+               bp += MAXDIGS;
+               *bp = '0';
+               return(bp);
+       }
+       
+       /* develop the leading digit of the value in "n" */
+       n = 0;
+       while (value < 0) {
+               value -= BIG;   /* will eventually underflow */
+               n++;
+       }
+       while ((lval = value - BIG) >= 0) {
+               value = lval;
+               n++;
+       }
+       
+       /* stash it in buffer[1] to allow for a sign */
+       bp[1] = n + '0';
+       /*
+        * Now develop the rest of the digits. Since speed counts here,
+        * we do it in two loops. The first gets "value" down until it
+        * is no larger than MAXINT. The second one uses integer divides
+        * rather than long divides to speed it up.
+        */
+       bp += MAXDIGS + 1;
+       while (value > MAXINT) {
+               *--bp = (int)(value % 10) + '0';
+               value /= 10;
+       }
+       
+       /* cannot lose precision */
+       svalue = value;
+       while (svalue > 0) {
+               *--bp = (svalue % 10) + '0';
+               svalue /= 10;
+       }
+       
+       /* fill in intermediate zeroes if needed */
+       if (buffer[1] != '0') {
+               while (bp > buffer + 2)
+                       *--bp = '0';
+               --bp;
+       }
+       return(bp);
+}
+
+/*
+ * This program sends string "s" to putchar. The character after
+ * the end of "s" is given by "send". This allows the size of the
+ * field to be computed; it is stored in "alen". "width" contains the
+ * user specified length. If width<alen, the width will be taken to
+ * be alen. "sign" is zero if the string is to be right-justified
+ * in the field, nonzero if it is to be left-justified. "fill" is
+ * 0 if the string is to be padded with '0', positive if it is to be
+ * padded with ' ', and negative if an initial '-' should appear before
+ * any padding in right-justification (to avoid printing "-3" as
+ * "000-3" where "-0003" was intended).
+ */
+_p_emit(s, send)
+       register char *s;
+       char *send;
+{
+       char cfill;
+       register int alen;
+       int npad;
+       
+       alen = send - s;
+       if (alen > width)
+               width = alen;
+       cfill = fill>0? ' ': '0';
+       
+       /* we may want to print a leading '-' before anything */
+       if (*s == '-' && fill < 0) {
+               putchar(*s++);
+               alen--;
+               width--;
+       }
+       npad = width - alen;
+       
+       /* emit any leading pad characters */
+       if (!sign)
+               while (--npad >= 0)
+                       putchar(cfill);
+                       
+       /* emit the string itself */
+       while (--alen >= 0)
+               putchar(*s++);
+               
+       /* emit trailing pad characters */
+       if (sign)
+               while (--npad >= 0)
+                       putchar(cfill);
+}