From 27704fe4c77e63607f86d8d3399b72a14b1ff5ae Mon Sep 17 00:00:00 2001 From: Bill Joy Date: Fri, 4 Jan 1980 02:38:10 -0800 Subject: [PATCH] BSD 3 development 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 --- usr/src/cmd/ex/:rofix | 3 + usr/src/cmd/ex/READ_ME | 43 ++ usr/src/cmd/ex/errs | 9 + usr/src/cmd/ex/ex.c | 462 ++++++++++++ usr/src/cmd/ex/ex.h | 351 +++++++++ usr/src/cmd/ex/ex_addr.c | 299 ++++++++ usr/src/cmd/ex/ex_argv.h | 26 + usr/src/cmd/ex/ex_cmds.c | 791 ++++++++++++++++++++ usr/src/cmd/ex/ex_cmds2.c | 508 +++++++++++++ usr/src/cmd/ex/ex_cmdsub.c | 1143 +++++++++++++++++++++++++++++ usr/src/cmd/ex/ex_data.c | 66 ++ usr/src/cmd/ex/ex_get.c | 269 +++++++ usr/src/cmd/ex/ex_io.c | 1047 ++++++++++++++++++++++++++ usr/src/cmd/ex/ex_put.c | 888 +++++++++++++++++++++++ usr/src/cmd/ex/ex_re.c | 880 ++++++++++++++++++++++ usr/src/cmd/ex/ex_re.h | 66 ++ usr/src/cmd/ex/ex_set.c | 197 +++++ usr/src/cmd/ex/ex_subr.c | 760 +++++++++++++++++++ usr/src/cmd/ex/ex_temp.c | 568 +++++++++++++++ usr/src/cmd/ex/ex_temp.h | 114 +++ usr/src/cmd/ex/ex_tty.c | 153 ++++ usr/src/cmd/ex/ex_tty.h | 126 ++++ usr/src/cmd/ex/ex_tune.h | 111 +++ usr/src/cmd/ex/ex_v.c | 374 ++++++++++ usr/src/cmd/ex/ex_vadj.c | 1047 ++++++++++++++++++++++++++ usr/src/cmd/ex/ex_vars.h | 37 + usr/src/cmd/ex/ex_vget.c | 546 ++++++++++++++ usr/src/cmd/ex/ex_vis.h | 252 +++++++ usr/src/cmd/ex/ex_vmain.c | 1196 ++++++++++++++++++++++++++++++ usr/src/cmd/ex/ex_voperate.c | 828 +++++++++++++++++++++ usr/src/cmd/ex/ex_vops.c | 812 +++++++++++++++++++++ usr/src/cmd/ex/ex_vops2.c | 782 ++++++++++++++++++++ usr/src/cmd/ex/ex_vops3.c | 539 ++++++++++++++ usr/src/cmd/ex/ex_vput.c | 1331 ++++++++++++++++++++++++++++++++++ usr/src/cmd/ex/ex_vwind.c | 460 ++++++++++++ usr/src/cmd/ex/expreserve.c | 358 +++++++++ usr/src/cmd/ex/makefile | 136 ++++ usr/src/cmd/ex/makeoptions | 40 + usr/src/cmd/ex/popen.c | 42 ++ usr/src/cmd/ex/printf.c | 340 +++++++++ 40 files changed, 18000 insertions(+) create mode 100644 usr/src/cmd/ex/:rofix create mode 100644 usr/src/cmd/ex/READ_ME create mode 100644 usr/src/cmd/ex/errs create mode 100644 usr/src/cmd/ex/ex.c create mode 100644 usr/src/cmd/ex/ex.h create mode 100644 usr/src/cmd/ex/ex_addr.c create mode 100644 usr/src/cmd/ex/ex_argv.h create mode 100644 usr/src/cmd/ex/ex_cmds.c create mode 100644 usr/src/cmd/ex/ex_cmds2.c create mode 100644 usr/src/cmd/ex/ex_cmdsub.c create mode 100644 usr/src/cmd/ex/ex_data.c create mode 100644 usr/src/cmd/ex/ex_get.c create mode 100644 usr/src/cmd/ex/ex_io.c create mode 100644 usr/src/cmd/ex/ex_put.c create mode 100644 usr/src/cmd/ex/ex_re.c create mode 100644 usr/src/cmd/ex/ex_re.h create mode 100644 usr/src/cmd/ex/ex_set.c create mode 100644 usr/src/cmd/ex/ex_subr.c create mode 100644 usr/src/cmd/ex/ex_temp.c create mode 100644 usr/src/cmd/ex/ex_temp.h create mode 100644 usr/src/cmd/ex/ex_tty.c create mode 100644 usr/src/cmd/ex/ex_tty.h create mode 100644 usr/src/cmd/ex/ex_tune.h create mode 100644 usr/src/cmd/ex/ex_v.c create mode 100644 usr/src/cmd/ex/ex_vadj.c create mode 100644 usr/src/cmd/ex/ex_vars.h create mode 100644 usr/src/cmd/ex/ex_vget.c create mode 100644 usr/src/cmd/ex/ex_vis.h create mode 100644 usr/src/cmd/ex/ex_vmain.c create mode 100644 usr/src/cmd/ex/ex_voperate.c create mode 100644 usr/src/cmd/ex/ex_vops.c create mode 100644 usr/src/cmd/ex/ex_vops2.c create mode 100644 usr/src/cmd/ex/ex_vops3.c create mode 100644 usr/src/cmd/ex/ex_vput.c create mode 100644 usr/src/cmd/ex/ex_vwind.c create mode 100644 usr/src/cmd/ex/expreserve.c create mode 100644 usr/src/cmd/ex/makefile create mode 100755 usr/src/cmd/ex/makeoptions create mode 100644 usr/src/cmd/ex/popen.c create mode 100644 usr/src/cmd/ex/printf.c diff --git a/usr/src/cmd/ex/:rofix b/usr/src/cmd/ex/:rofix new file mode 100644 index 0000000000..cd5fa24120 --- /dev/null +++ b/usr/src/cmd/ex/:rofix @@ -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 index 0000000000..a6141c2af0 --- /dev/null +++ b/usr/src/cmd/ex/READ_ME @@ -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 index 0000000000..1c4618b31c --- /dev/null +++ b/usr/src/cmd/ex/errs @@ -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 index 0000000000..e74b5c046c --- /dev/null +++ b/usr/src/cmd/ex/ex.c @@ -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 index 0000000000..9493541ce1 --- /dev/null +++ b/usr/src/cmd/ex/ex.h @@ -0,0 +1,351 @@ +/* Copyright (c) 1979 Regents of the University of California */ +#ifdef V6 +#include +#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 +#include +#include +#include +#include +#include +#include + +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 + 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 index 0000000000..5621e5e590 --- /dev/null +++ b/usr/src/cmd/ex/ex_addr.c @@ -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 index 0000000000..666045b3a5 --- /dev/null +++ b/usr/src/cmd/ex/ex_argv.h @@ -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 index 0000000000..d577f8976c --- /dev/null +++ b/usr/src/cmd/ex/ex_cmds.c @@ -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 index 0000000000..95deb537d4 --- /dev/null +++ b/usr/src/cmd/ex/ex_cmds2.c @@ -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 index 0000000000..09c01d9d2f --- /dev/null +++ b/usr/src/cmd/ex/ex_cmdsub.c @@ -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 index 0000000000..402d208b28 --- /dev/null +++ b/usr/src/cmd/ex/ex_data.c @@ -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 index 0000000000..4d19535174 --- /dev/null +++ b/usr/src/cmd/ex/ex_get.c @@ -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 index 0000000000..d22f82ef87 --- /dev/null +++ b/usr/src/cmd/ex/ex_io.c @@ -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 index 0000000000..c184d883e5 --- /dev/null +++ b/usr/src/cmd/ex/ex_put.c @@ -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 index 0000000000..93abfaa00b --- /dev/null +++ b/usr/src/cmd/ex/ex_re.c @@ -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 index 0000000000..500d5d5a96 --- /dev/null +++ b/usr/src/cmd/ex/ex_re.h @@ -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 index 0000000000..721836c551 --- /dev/null +++ b/usr/src/cmd/ex/ex_set.c @@ -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 index 0000000000..1e17f4cb29 --- /dev/null +++ b/usr/src/cmd/ex/ex_subr.c @@ -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 index 0000000000..e8f87804f3 --- /dev/null +++ b/usr/src/cmd/ex/ex_temp.c @@ -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 index 0000000000..7d4c751807 --- /dev/null +++ b/usr/src/cmd/ex/ex_temp.h @@ -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 index 0000000000..62763af561 --- /dev/null +++ b/usr/src/cmd/ex/ex_tty.c @@ -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 index 0000000000..0dc41fd0be --- /dev/null +++ b/usr/src/cmd/ex/ex_tty.h @@ -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 index 0000000000..192e9b8fe7 --- /dev/null +++ b/usr/src/cmd/ex/ex_tune.h @@ -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 index 0000000000..c42808b9f2 --- /dev/null +++ b/usr/src/cmd/ex/ex_v.c @@ -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 index 0000000000..78373397da --- /dev/null +++ b/usr/src/cmd/ex/ex_vadj.c @@ -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 index 0000000000..c3bd859427 --- /dev/null +++ b/usr/src/cmd/ex/ex_vars.h @@ -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 index 0000000000..a4645a03e4 --- /dev/null +++ b/usr/src/cmd/ex/ex_vget.c @@ -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 index 0000000000..9fac3bcfcb --- /dev/null +++ b/usr/src/cmd/ex/ex_vis.h @@ -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 index 0000000000..e894dee246 --- /dev/null +++ b/usr/src/cmd/ex/ex_vmain.c @@ -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 current line to top + * z like z + * 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 index 0000000000..f65d7b2825 --- /dev/null +++ b/usr/src/cmd/ex/ex_voperate.c @@ -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 index 0000000000..92a5f6829a --- /dev/null +++ b/usr/src/cmd/ex/ex_vops.c @@ -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 index 0000000000..86334bc437 --- /dev/null +++ b/usr/src/cmd/ex/ex_vops2.c @@ -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 index 0000000000..b0e96971e6 --- /dev/null +++ b/usr/src/cmd/ex/ex_vops3.c @@ -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 index 0000000000..cadd33ab3c --- /dev/null +++ b/usr/src/cmd/ex/ex_vput.c @@ -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 index 0000000000..18f1104321 --- /dev/null +++ b/usr/src/cmd/ex/ex_vwind.c @@ -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 index 0000000000..373aa699c0 --- /dev/null +++ b/usr/src/cmd/ex/expreserve.c @@ -0,0 +1,358 @@ +/* Copyright (c) 1979 Regents of the University of California */ +#include +#include +#include +#include +#include +#include +#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 index 0000000000..c62b9d799b --- /dev/null +++ b/usr/src/cmd/ex/makefile @@ -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 index 0000000000..84d2639389 --- /dev/null +++ b/usr/src/cmd/ex/makeoptions @@ -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 index 0000000000..26d89eb26b --- /dev/null +++ b/usr/src/cmd/ex/popen.c @@ -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 index 0000000000..be24ebe25e --- /dev/null +++ b/usr/src/cmd/ex/printf.c @@ -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 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); +} -- 2.20.1