Path: utzoo!utgpu!cs.utexas.edu!uunet!zephyr.ens.tek.com!tektronix!nosun!qiclab!pdxgate!eecs.cs.pdx.edu!kirkenda From: kirkenda@eecs.cs.pdx.edu (Steve Kirkendall) Newsgroups: alt.sources Subject: Elvis 1.4, part 7 of 8 Message-ID: <831@pdxgate.UUCP> Date: 3 Dec 90 21:35:35 GMT Sender: news@pdxgate.UUCP Reply-To: kirkenda@eecs.cs.pdx.edu (Steve Kirkendall) Organization: Portland State University, Portland, OR Lines: 3204 # --------------------------- cut here ---------------------------- # This is a shar archive. To unpack it, save it to a file, and delete # anything above the "cut here" line. Then run sh on the file. # # -rw-r--r-- 1 kirkenda 16871 Dec 2 17:57 regexp.c # -rw-r--r-- 1 kirkenda 579 Dec 2 17:57 regexp.h # -rw-r--r-- 1 kirkenda 3614 Dec 2 17:57 regsub.c # -rw-r--r-- 1 kirkenda 8849 Dec 2 17:57 system.c # -rw-r--r-- 1 kirkenda 3767 Dec 2 17:57 tinytcap.c # -rw-r--r-- 1 kirkenda 16181 Dec 2 17:57 tio.c # -rw-r--r-- 1 kirkenda 12770 Dec 2 17:57 tmp.c # if test -f regexp.c -a "$1" != -f then echo Will not overwrite regexp.c else echo Extracting regexp.c sed 's/^X//' >regexp.c <<\eof X/* regexp.c */ X X/* This file contains the code that compiles regular expressions and executes X * them. It supports the same syntax and features as vi's regular expression X * code. Specifically, the meta characters are: X * ^ matches the beginning of a line X * $ matches the end of a line X * \< matches the beginning of a word X * \> matches the end of a word X * . matches any single character X * [] matches any character in a character class X * \( delimits the start of a subexpression X * \) delimits the end of a subexpression X * * repeats the preceding 0 or more times X * NOTE: You cannot follow a \) with a *. X * X * The physical structure of a compiled RE is as follows: X * - First, there is a one-byte value that says how many character classes X * are used in this regular expression X * - Next, each character class is stored as a bitmap that is 256 bits X * (32 bytes) long. X * - A mixture of literal characters and compiled meta characters follows. X * This begins with M_BEGIN(0) and ends with M_END(0). All meta chars X * are stored as a \n followed by a one-byte code, so they take up two X * bytes apiece. Literal characters take up one byte apiece. \n can't X * be used as a literal character. X * X * If NO_MAGIC is defined, then a different set of functions is used instead. X * That right, this file contains TWO versions of the code. X */ X X#include X#include X#include "config.h" X#include "vi.h" X#include "regexp.h" X X X Xstatic char *previous; /* the previous regexp, used when null regexp is given */ X X X#ifndef NO_MAGIC X/* THE REAL REGEXP PACKAGE IS USED UNLESS "NO_MAGIC" IS DEFINED */ X X/* These are used to classify or recognize meta-characters */ X#define META '\0' X#define BASE_META(m) ((m) - 256) X#define INT_META(c) ((c) + 256) X#define IS_META(m) ((m) >= 256) X#define IS_CLASS(m) ((m) >= M_CLASS(0) && (m) <= M_CLASS(9)) X#define IS_START(m) ((m) >= M_START(0) && (m) <= M_START(9)) X#define IS_END(m) ((m) >= M_END(0) && (m) <= M_END(9)) X#define IS_CLOSURE(m) ((m) >= M_SPLAT && (m) <= M_QMARK) X#define ADD_META(s,m) (*(s)++ = META, *(s)++ = BASE_META(m)) X#define GET_META(s) (*(s) == META ? INT_META(*++(s)) : *s) X X/* These are the internal codes used for each type of meta-character */ X#define M_BEGLINE 256 /* internal code for ^ */ X#define M_ENDLINE 257 /* internal code for $ */ X#define M_BEGWORD 258 /* internal code for \< */ X#define M_ENDWORD 259 /* internal code for \> */ X#define M_ANY 260 /* internal code for . */ X#define M_SPLAT 261 /* internal code for * */ X#define M_PLUS 262 /* internal code for \+ */ X#define M_QMARK 263 /* internal code for \? */ X#define M_CLASS(n) (264+(n)) /* internal code for [] */ X#define M_START(n) (274+(n)) /* internal code for \( */ X#define M_END(n) (284+(n)) /* internal code for \) */ X X/* These are used during compilation */ Xstatic int class_cnt; /* used to assign class IDs */ Xstatic int start_cnt; /* used to assign start IDs */ Xstatic int end_stk[NSUBEXP];/* used to assign end IDs */ Xstatic int end_sp; Xstatic char *retext; /* points to the text being compiled */ X X/* error-handling stuff */ Xjmp_buf errorhandler; X#define FAIL(why) regerror(why); longjmp(errorhandler, 1) X X X X X X/* This function builds a bitmap for a particular class */ Xstatic char *makeclass(text, bmap) X REG char *text; /* start of the class */ X REG char *bmap; /* the bitmap */ X{ X REG int i; X int complement = 0; X X# if TRACE X printf("makeclass(\"%s\", 0x%lx)\n", text, (long)bmap); X# endif X X /* zero the bitmap */ X for (i = 0; bmap && i < 32; i++) X { X bmap[i] = 0; X } X X /* see if we're going to complement this class */ X if (*text == '^') X { X text++; X complement = 1; X } X X /* add in the characters */ X while (*text && *text != ']') X { X /* is this a span of characters? */ X if (text[1] == '-' && text[2]) X { X /* spans can't be backwards */ X if (text[0] > text[2]) X { X FAIL("Backwards span in []"); X } X X /* add each character in the span to the bitmap */ X for (i = text[0]; bmap && i <= text[2]; i++) X { X bmap[i >> 3] |= (1 << (i & 7)); X } X X /* move past this span */ X text += 3; X } X else X { X /* add this single character to the span */ X i = *text++; X if (bmap) X { X bmap[i >> 3] |= (1 << (i & 7)); X } X } X } X X /* make sure the closing ] is missing */ X if (*text++ != ']') X { X FAIL("] missing"); X } X X /* if we're supposed to complement this class, then do so */ X if (complement && bmap) X { X for (i = 0; i < 32; i++) X { X bmap[i] = ~bmap[i]; X } X } X X return text; X} X X X X X/* This function gets the next character or meta character from a string. X * The pointer is incremented by 1, or by 2 for \-quoted characters. For [], X * a bitmap is generated via makeclass() (if re is given), and the X * character-class text is skipped. X */ Xstatic int gettoken(sptr, re) X char **sptr; X regexp *re; X{ X int c; X X c = **sptr; X ++*sptr; X if (c == '\\') X { X c = **sptr; X ++*sptr; X switch (c) X { X case '<': X return M_BEGWORD; X X case '>': X return M_ENDWORD; X X case '(': X if (start_cnt >= NSUBEXP) X { X FAIL("Too many \\(s"); X } X end_stk[end_sp++] = start_cnt; X return M_START(start_cnt++); X X case ')': X if (end_sp <= 0) X { X FAIL("Mismatched \\)"); X } X return M_END(end_stk[--end_sp]); X X case '*': X return (*o_magic ? c : M_SPLAT); X X case '.': X return (*o_magic ? c : M_ANY); X X case '+': X return M_PLUS; X X case '?': X return M_QMARK; X X default: X return c; X } X } X else if (*o_magic) X { X switch (c) X { X case '^': X if (*sptr == retext + 1) X { X return M_BEGLINE; X } X return c; X X case '$': X if (!**sptr) X { X return M_ENDLINE; X } X return c; X X case '.': X return M_ANY; X X case '*': X return M_SPLAT; X X case '[': X /* make sure we don't have too many classes */ X if (class_cnt >= 10) X { X FAIL("Too many []s"); X } X X /* process the character list for this class */ X if (re) X { X /* generate the bitmap for this class */ X *sptr = makeclass(*sptr, re->program + 1 + 32 * class_cnt); X } X else X { X /* skip to end of the class */ X *sptr = makeclass(*sptr, (char *)0); X } X return M_CLASS(class_cnt++); X X default: X return c; X } X } X else /* unquoted nomagic */ X { X switch (c) X { X case '^': X if (*sptr == retext + 1) X { X return M_BEGLINE; X } X return c; X X case '$': X if (!**sptr) X { X return M_ENDLINE; X } X return c; X X default: X return c; X } X } X /*NOTREACHED*/ X} X X X X X/* This function calculates the number of bytes that will be needed for a X * compiled RE. Its argument is the uncompiled version. It is not clever X * about catching syntax errors; that is done in a later pass. X */ Xstatic unsigned calcsize(text) X char *text; X{ X unsigned size; X int token; X X retext = text; X class_cnt = 0; X start_cnt = 1; X end_sp = 0; X size = 5; X while ((token = gettoken(&text, (regexp *)0)) != 0) X { X if (IS_CLASS(token)) X { X size += 34; X } X else if (IS_META(token)) X { X size += 2; X } X else X { X size++; X } X } X X return size; X} X X X X/* This function compiles a regexp. */ Xregexp *regcomp(text) X char *text; X{ X int needfirst; X unsigned size; X int token; X int peek; X char *build; X regexp *re; X X X /* prepare for error handling */ X re = (regexp *)0; X if (setjmp(errorhandler)) X { X if (re) X { X free(re); X } X return (regexp *)0; X } X X /* if an empty regexp string was given, use the previous one */ X if (*text == 0) X { X if (!previous) X { X FAIL("No previous RE"); X } X text = previous; X } X else /* non-empty regexp given, so remember it */ X { X if (previous) X free(previous); X previous = (char *)malloc((unsigned)(strlen(text) + 1)); X if (previous) X strcpy(previous, text); X } X X /* allocate memory */ X class_cnt = 0; X start_cnt = 1; X end_sp = 0; X retext = text; X size = calcsize(text) + sizeof(regexp); X#ifdef lint X re = ((regexp *)0) + size; X#else X re = (regexp *)malloc((unsigned)size); X#endif X if (!re) X { X FAIL("Not enough memory for this RE"); X } X X /* compile it */ X build = &re->program[1 + 32 * class_cnt]; X re->program[0] = class_cnt; X for (token = 0; token < NSUBEXP; token++) X { X re->startp[token] = re->endp[token] = (char *)0; X } X re->first = 0; X re->bol = 0; X re->minlen = 0; X needfirst = 1; X class_cnt = 0; X start_cnt = 1; X end_sp = 0; X retext = text; X for (token = M_START(0), peek = gettoken(&text, re); X token; X token = peek, peek = gettoken(&text, re)) X { X /* special processing for the closure operator */ X if (IS_CLOSURE(peek)) X { X /* detect misuse of closure operator */ X if (IS_START(token)) X { X FAIL("* or \\+ or \\? follows nothing"); X } X else if (IS_META(token) && token != M_ANY && !IS_CLASS(token)) X { X FAIL("* or \\+ or \\? can only follow a normal character or . or []"); X } X X /* it is okay -- make it prefix instead of postfix */ X ADD_META(build, peek); X X /* take care of "needfirst" - is this the first char? */ X if (needfirst && peek == M_PLUS && !IS_META(token)) X { X re->first = token; X } X needfirst = 0; X X /* we used "peek" -- need to refill it */ X peek = gettoken(&text, re); X if (IS_CLOSURE(peek)) X { X FAIL("* or \\+ or \\? doubled up"); X } X } X else if (!IS_META(token)) X { X /* normal char is NOT argument of closure */ X if (needfirst) X { X re->first = token; X needfirst = 0; X } X re->minlen++; X } X else if (token == M_ANY || IS_CLASS(token)) X { X /* . or [] is NOT argument of closure */ X needfirst = 0; X re->minlen++; X } X X /* the "token" character is not closure -- process it normally */ X if (token == M_BEGLINE) X { X /* set the BOL flag instead of storing M_BEGLINE */ X re->bol = 1; X } X else if (IS_META(token)) X { X ADD_META(build, token); X } X else X { X *build++ = token; X } X } X X /* end it with a \) which MUST MATCH the opening \( */ X ADD_META(build, M_END(0)); X if (end_sp > 0) X { X FAIL("Not enough \\)s"); X } X X return re; X} X X X X/*---------------------------------------------------------------------------*/ X X X/* This function checks for a match between a character and a token which is X * known to represent a single character. It returns 0 if they match, or X * 1 if they don't. X */ Xint match1(re, ch, token) X regexp *re; X REG char ch; X REG int token; X{ X if (!ch) X { X /* the end of a line can't match any RE of width 1 */ X return 1; X } X if (token == M_ANY) X { X return 0; X } X else if (IS_CLASS(token)) X { X if (re->program[1 + 32 * (token - M_CLASS(0)) + (ch >> 3)] & (1 << (ch & 7))) X return 0; X } X else if (ch == token X || (*o_ignorecase && isupper(ch) && tolower(ch) == token)) X { X return 0; X } X return 1; X} X X X X/* This function checks characters up to and including the next closure, at X * which point it does a recursive call to check the rest of it. This function X * returns 0 if everything matches, or 1 if something doesn't match. X */ Xint match(re, str, prog, here) X regexp *re; /* the regular expression */ X char *str; /* the string */ X REG char *prog; /* a portion of re->program, an compiled RE */ X REG char *here; /* a portion of str, the string to compare it to */ X{ X REG int token; X REG int nmatched; X REG int closure; X X for (token = GET_META(prog); !IS_CLOSURE(token); prog++, token = GET_META(prog)) X { X switch (token) X { X /*case M_BEGLINE: can't happen; re->bol is used instead */ X case M_ENDLINE: X if (*here) X return 1; X break; X X case M_BEGWORD: X if (here != str && X (here[-1] == '_' || X isascii(here[-1]) && isalnum(here[-1]))) X return 1; X break; X X case M_ENDWORD: X if (here[0] == '_' || isascii(here[0]) && isalnum(here[0])) X return 1; X break; X X case M_START(0): X case M_START(1): X case M_START(2): X case M_START(3): X case M_START(4): X case M_START(5): X case M_START(6): X case M_START(7): X case M_START(8): X case M_START(9): X re->startp[token - M_START(0)] = (char *)here; X break; X X case M_END(0): X case M_END(1): X case M_END(2): X case M_END(3): X case M_END(4): X case M_END(5): X case M_END(6): X case M_END(7): X case M_END(8): X case M_END(9): X re->endp[token - M_END(0)] = (char *)here; X if (token == M_END(0)) X { X return 0; X } X break; X X default: /* literal, M_CLASS(n), or M_ANY */ X if (match1(re, *here, token) != 0) X return 1; X here++; X } X } X X /* C L O S U R E */ X X /* step 1: see what we have to match against, and move "prog" to point X * the the remainder of the compiled RE. X */ X closure = token; X prog++, token = GET_META(prog); X prog++; X X /* step 2: see how many times we can match that token against the string */ X for (nmatched = 0; X (closure != M_QMARK || nmatched < 1) && *here && match1(re, *here, token) == 0; X nmatched++, here++) X { X } X X /* step 3: try to match the remainder, and back off if it doesn't */ X while (nmatched >= 0 && match(re, str, prog, here) != 0) X { X nmatched--; X here--; X } X X /* so how did it work out? */ X if (nmatched >= ((closure == M_PLUS) ? 1 : 0)) X return 0; X return 1; X} X X X X/* This function searches through a string for text that matches an RE. */ Xint regexec(re, str, bol) X regexp *re; /* the compiled regexp to search for */ X char *str; /* the string to search through */ X int bol; /* boolean: does str start at the beginning of a line? */ X{ X char *prog; /* the entry point of re->program */ X int len; /* length of the string */ X REG char *here; X X /* if must start at the beginning of a line, and this isn't, then fail */ X if (re->bol && !bol) X { X return 0; X } X X len = strlen(str); X prog = re->program + 1 + 32 * re->program[0]; X X /* search for the RE in the string */ X if (re->bol) X { X /* must occur at BOL */ X if ((re->first X && match1(re, *(char *)str, re->first))/* wrong first letter? */ X || len < re->minlen /* not long enough? */ X || match(re, (char *)str, prog, str)) /* doesn't match? */ X return 0; /* THEN FAIL! */ X } X#ifndef CRUNCH X else if (!*o_ignorecase) X { X /* can occur anywhere in the line, noignorecase */ X for (here = (char *)str; X (re->first && re->first != *here) X || match(re, (char *)str, prog, here); X here++, len--) X { X if (len < re->minlen) X return 0; X } X } X#endif X else X { X /* can occur anywhere in the line, ignorecase */ X for (here = (char *)str; X (re->first && match1(re, *here, (int)re->first)) X || match(re, (char *)str, prog, here); X here++, len--) X { X if (len < re->minlen) X return 0; X } X } X X /* if we didn't fail, then we must have succeeded */ X return 1; X} X X#else /* NO_MAGIC */ X Xregexp *regcomp(exp) X char *exp; X{ X char *src; X char *dest; X regexp *re; X int i; X X /* allocate a big enough regexp structure */ X#ifdef lint X re = (regexp *)0; X#else X re = (regexp *)malloc((unsigned)(strlen(exp) + 1 + sizeof(struct regexp))); X#endif X if (!re) X { X regerror("Could not malloc a regexp structure"); X return (regexp *)0; X } X X /* initialize all fields of the structure */ X for (i = 0; i < NSUBEXP; i++) X { X re->startp[i] = re->endp[i] = (char *)0; X } X re->minlen = 0; X re->first = 0; X re->bol = 0; X X /* copy the string into it, translating ^ and $ as needed */ X for (src = exp, dest = re->program + 1; *src; src++) X { X switch (*src) X { X case '^': X if (src == exp) X { X re->bol += 1; X } X else X { X *dest++ = '^'; X re->minlen++; X } X break; X X case '$': X if (!src[1]) X { X re->bol += 2; X } X else X { X *dest++ = '$'; X re->minlen++; X } X break; X X case '\\': X if (src[1]) X { X *dest++ = *++src; X re->minlen++; X } X else X { X regerror("extra \\ at end of regular expression"); X } X break; X X default: X *dest++ = *src; X re->minlen++; X } X } X *dest = '\0'; X X return re; X} X X X/* This "helper" function checks for a match at a given location. It returns X * 1 if it matches, 0 if it doesn't match here but might match later on in the X * string, or -1 if it could not possibly match X */ Xstatic int reghelp(prog, string, bolflag) X struct regexp *prog; X char *string; X int bolflag; X{ X char *scan; X char *str; X X /* if ^, then require bolflag */ X if ((prog->bol & 1) && !bolflag) X { X return -1; X } X X /* if it matches, then it will start here */ X prog->startp[0] = string; X X /* compare, possibly ignoring case */ X if (*o_ignorecase) X { X for (scan = &prog->program[1]; *scan; scan++, string++) X if (tolower(*scan) != tolower(*string)) X return *string ? 0 : -1; X } X else X { X for (scan = &prog->program[1]; *scan; scan++, string++) X if (*scan != *string) X return *string ? 0 : -1; X } X X /* if $, then require string to end here, too */ X if ((prog->bol & 2) && *string) X { X return 0; X } X X /* if we get to here, it matches */ X prog->endp[0] = string; X return 1; X} X X X Xint regexec(prog, string, bolflag) X struct regexp *prog; X char *string; X int bolflag; X{ X int rc; X X /* keep trying to match it */ X for (rc = reghelp(prog, string, bolflag); rc == 0; rc = reghelp(prog, string, 0)) X { X string++; X } X X /* did we match? */ X return rc == 1; X} X#endif eof if test `wc -c regexp.h <<\eof X/* X * Definitions etc. for regexp(3) routines. X * X * Caveat: this is V8 regexp(3) [actually, a reimplementation thereof], X * not the System V one. X */ X#define NSUBEXP 10 X Xtypedef struct regexp { X char *startp[NSUBEXP]; X char *endp[NSUBEXP]; X int minlen; /* length of shortest possible match */ X char first; /* first character, if known; else \0 */ X char bol; /* boolean: must start at beginning of line? */ X char program[1]; /* Unwarranted chumminess with compiler. */ X} regexp; X Xextern regexp *regcomp(); Xextern int regexec(); Xextern void regsub(); Xextern void regerror(); eof if test `wc -c regsub.c <<\eof X/* regsub.c */ X X/* This file contains the regsub() function, which performs substitutions X * after a regexp match has been found. X */ X X#include X#include "config.h" X#include "vi.h" X#include "regexp.h" X Xstatic char *previous; /* a copy of the text from the previous substitution */ X X/* perform substitutions after a regexp match */ Xvoid regsub(re, src, dst) X regexp *re; X REG char *src; X REG char *dst; X{ X REG char *cpy; X REG char *end; X REG char c; X char *start; X#ifndef CRUNCH X int mod; X X mod = 0; X#endif X X start = src; X while ((c = *src++) != '\0') X { X#ifndef NO_MAGIC X /* recognize any meta characters */ X if (c == '&' && *o_magic) X { X cpy = re->startp[0]; X end = re->endp[0]; X } X else if (c == '~' && *o_magic) X { X cpy = previous; X if (cpy) X end = cpy + strlen(cpy); X } X else X#endif /* not NO_MAGIC */ X if (c == '\\') X { X c = *src++; X switch (c) X { X#ifndef NO_MAGIC X case '0': X case '1': X case '2': X case '3': X case '4': X case '5': X case '6': X case '7': X case '8': X case '9': X /* \0 thru \9 mean "copy subexpression" */ X c -= '0'; X cpy = re->startp[c]; X end = re->endp[c]; X break; X# ifndef CRUNCH X case 'U': X case 'u': X case 'L': X case 'l': X /* \U and \L mean "convert to upper/lowercase" */ X mod = c; X continue; X X case 'E': X case 'e': X /* \E ends the \U or \L */ X mod = 0; X continue; X# endif /* not CRUNCH */ X case '&': X /* "\&" means "original text" */ X if (*o_magic) X { X *dst++ = c; X continue; X } X cpy = re->startp[0]; X end = re->endp[0]; X break; X X case '~': X /* "\~" means "previous text, if any" */ X if (*o_magic) X { X *dst++ = c; X continue; X } X cpy = previous; X if (cpy) X end = cpy + strlen(cpy); X break; X#else /* NO_MAGIC */ X case '&': X /* "\&" means "original text" */ X cpy = re->startp[0]; X end = re->endp[0]; X break; X X case '~': X /* "\~" means "previous text, if any" */ X cpy = previous; X if (cpy) X end = cpy + strlen(cpy); X break; X#endif /* NO_MAGIC */ X default: X /* ordinary char preceded by backslash */ X *dst++ = c; X continue; X } X } X else X { X /* ordinary character, so just copy it */ X *dst++ = c; X continue; X } X X /* Note: to reach this point in the code, we must have evaded X * all "continue" statements. To do that, we must have hit X * a metacharacter that involves copying. X */ X X /* if there is nothing to copy, loop */ X if (!cpy) X continue; X X /* copy over a portion of the original */ X while (cpy < end) X { X#ifndef NO_MAGIC X# ifndef CRUNCH X switch (mod) X { X case 'U': X case 'u': X /* convert to uppercase */ X if (isascii(*cpy) && islower(*cpy)) X { X *dst++ = toupper(*cpy); X cpy++; X } X else X { X *dst++ = *cpy++; X } X break; X X case 'L': X case 'l': X /* convert to lowercase */ X if (isascii(*cpy) && isupper(*cpy)) X { X *dst++ = tolower(*cpy); X cpy++; X } X else X { X *dst++ = *cpy++; X } X break; X X default: X /* copy without any conversion */ X *dst++ = *cpy++; X } X X /* \u and \l end automatically after the first char */ X if (mod && (mod == 'u' || mod == 'l')) X { X mod = 0; X } X# else /* CRUNCH */ X *dst++ = *cpy++; X# endif /* CRUNCH */ X#else /* NO_MAGIC */ X *dst++ = *cpy++; X#endif /* NO_MAGIC */ X } X } X *dst = '\0'; X X /* remember what text we inserted this time */ X if (previous) X free(previous); X previous = (char *)malloc((unsigned)(strlen(start) + 1)); X if (previous) X strcpy(previous, start); X} eof if test `wc -c system.c <<\eof X/* system.c -- UNIX version */ X X/* Author: X * Steve Kirkendall X * 14407 SW Teal Blvd. #C X * Beaverton, OR 97005 X * kirkenda@cs.pdx.edu X */ X X X/* This file contains a new version of the system() function and related stuff. X * X * Entry points are: X * system(cmd) - run a single shell command X * wildcard(names) - expand wildcard characters in filanames X * filter(m,n,cmd) - run text lines through a filter program X * X * This is probably the single least portable file in the program. The code X * shown here should work correctly if it links at all; it will work on UNIX X * and any O.S./Compiler combination which adheres to UNIX forking conventions. X */ X X#include "config.h" X#include "vi.h" X#include Xextern char **environ; X X#if ANY_UNIX X X/* This is a new version of the system() function. The only difference X * between this one and the library one is: this one uses the o_shell option. X */ Xint system(cmd) X char *cmd; /* a command to run */ X{ X int status; /* exit status of the command */ X X /* warn the user if the file hasn't been saved yet */ X if (*o_warn && tstflag(file, MODIFIED)) X { X if (mode == MODE_VI) X { X mode = MODE_COLON; X } X msg("Warning: \"%s\" has been modified but not yet saved", origname); X } X X signal(SIGINT, SIG_IGN); X switch (fork()) X { X case -1: /* error */ X msg("fork() failed"); X status = -1; X break; X X case 0: /* child */ X /* for the child, close all files except stdin/out/err */ X for (status = 3; status < 60 && (close(status), errno != EINVAL); status++) X { X } X X signal(SIGINT, SIG_DFL); X if (cmd == o_shell) X { X execle(o_shell, o_shell, (char *)0, environ); X } X else X { X execle(o_shell, o_shell, "-c", cmd, (char *)0, environ); X } X msg("execle(\"%s\", ...) failed", o_shell); X exit(1); /* if we get here, the exec failed */ X X default: /* parent */ X wait(&status); X signal(SIGINT, trapint); X } X X return status; X} X X/* This private function opens a pipe from a filter. It is similar to the X * system() function above, and to popen(cmd, "r"). X */ Xstatic int rpipe(cmd, in) X char *cmd; /* the filter command to use */ X int in; /* the fd to use for stdin */ X{ X int r0w1[2];/* the pipe fd's */ X X /* make the pipe */ X if (pipe(r0w1) < 0) X { X return -1; /* pipe failed */ X } X X /* The parent process (elvis) ignores signals while the filter runs. X * The child (the filter program) will reset this, so that it can X * catch the signal. X */ X signal(SIGINT, SIG_IGN); X X switch (fork()) X { X case -1: /* error */ X return -1; X X case 0: /* child */ X /* close the "read" end of the pipe */ X close(r0w1[0]); X X /* redirect stdout to go to the "write" end of the pipe */ X close(1); X dup(r0w1[1]); X close(2); X dup(r0w1[1]); X close(r0w1[1]); X X /* redirect stdin */ X if (in != 0) X { X close(0); X dup(in); X close(in); X } X X /* the filter should accept SIGINT signals */ X signal(SIGINT, SIG_DFL); X X /* exec the shell to run the command */ X execle(o_shell, o_shell, "-c", cmd, (char *)0, environ); X exit(1); /* if we get here, exec failed */ X X default: /* parent */ X /* close the "write" end of the pipe */ X close(r0w1[1]); X X return r0w1[0]; X } X} X X#endif /* non-DOS */ X X#if OSK X X/* This private function opens a pipe from a filter. It is similar to the X * system() function above, and to popen(cmd, "r"). X */ Xstatic int rpipe(cmd, in) X char *cmd; /* the filter command to use */ X int in; /* the fd to use for stdin */ X{ X X char **argblk; X char *p, *buffer, *command; X int stdinp, stdoutp; X unsigned addstack = 0; X int words, len, loop = 1; X int fp, pipe_pid; X extern int os9forkc(); X extern char *index(); X X command = cmd; X words = 0; X if ((buffer = (char*) malloc(strlen(cmd))) == (char*) 0) X return 0; X X do { X if (!(p = index(command, ' '))) { X loop--; X len = strlen(command); X } X else X len = p - command; X words++; X while (command[len] && command[len] == ' ') X len++; X if (!command[len]) X break; X command = command + len; X } X while (loop); X if ((argblk = (char **)malloc((words+1) * sizeof(char*))) == (char **)0) X return 0; X command = cmd; X words = 0; X do { X if (!(p = index(command, ' '))) { X loop--; X len = strlen(command); X } X else X len = p - command; X strncpy(buffer, command, len); X argblk[words++] = buffer; X buffer += len; X *buffer++ = '\0'; X while (command[len] && command[len] == ' ') X len++; X if (!command[len]) X break; X command = command + len; X } while (loop); X if (argblk[words - 1][0] == '#') X addstack = 1024 * atoi(&argblk[--words][1]); X argblk[words] = 0; X X stdoutp = dup(1); X close(1); /* close stdout */ X if ((fp = open("/pipe",S_IREAD)) < 0) { X dup(stdoutp); X close(stdoutp); X return 0; X } X if (in != 0) { X stdinp = dup(0); X close(0); X dup(in); X close(in); X } X pipe_pid = os9exec(os9forkc,argblk[0],argblk,environ,addstack,0,3); X if (pipe_pid == -1) { X fclose(fp); X dup(stdoutp); X close(stdoutp); X if (in != 0) { X close(0); X dup(stdinp); X } X return 0; X } X fp = (short)dup(1); /* save pipe */ X close(1); /* get rid of the pipe */ X dup(stdoutp); /* restore old stdout */ X close(stdoutp); /* close path to stdout copy */ X if (in != 0) { X close(0); X dup(stdinp); X } X return fp; X} X#endif X X#if ANY_UNIX || OSK X X/* This function closes the pipe opened by rpipe(), and returns 0 for success */ Xstatic int rpclose(fd) X int fd; X{ X int status; X X close(fd); X wait(&status); X signal(SIGINT, trapint); X return status; X} X X#endif /* non-DOS */ X X/* This function expands wildcards in a filename or filenames. It does this X * by running the "echo" command on the filenames via the shell; it is assumed X * that the shell will expand the names for you. If for any reason it can't X * run echo, then it returns the names unmodified. X */ X X#if MSDOS || TOS X#define PROG "wildcard " X#define PROGLEN 9 X#include X#else X#define PROG "echo " X#define PROGLEN 5 X#endif X Xchar *wildcard(names) X char *names; X{ X int i, j, fd; X REG char *s, *d; X X X /* build the echo command */ X if (names != tmpblk.c) X { X /* the names aren't in tmpblk.c, so we can do it the easy way */ X strcpy(tmpblk.c, PROG); X strcat(tmpblk.c, names); X } X else X { X /* the names are already in tmpblk.c, so shift them to make X * room for the word "echo " X */ X for (s = names + strlen(names) + 1, d = s + PROGLEN; s > names; ) X { X *--d = *--s; X } X strncpy(names, PROG, PROGLEN); X } X X /* run the command & read the resulting names */ X fd = rpipe(tmpblk.c, 0); X if (fd < 0) return names; X i = 0; X do X { X j = tread(fd, tmpblk.c + i, BLKSIZE - i); X i += j; X } while (j > 0); X X /* successful? */ X if (rpclose(fd) == 0 && j == 0 && i < BLKSIZE && i > 0) X { X tmpblk.c[i-1] = '\0'; /* "i-1" so we clip off the newline */ X return tmpblk.c; X } X else X { X return names; X } X} X X/* This function runs a range of lines through a filter program, and replaces X * the original text with the filtered version. As a special case, if "to" X * is MARK_UNSET, then it runs the filter program with stdin coming from X * /dev/null, and inserts any output lines. X */ Xint filter(from, to, cmd) X MARK from, to; /* the range of lines to filter */ X char *cmd; /* the filter command */ X{ X int scratch; /* fd of the scratch file */ X int fd; /* fd of the pipe from the filter */ X char scrout[50]; /* name of the scratch out file */ X MARK new; /* place where new text should go */ X int i; X X /* write the lines (if specified) to a temp file */ X if (to) X { X /* we have lines */ X#if MSDOS || TOS X strcpy(scrout, o_directory); X if ((i=strlen(scrout)) && strchr("\\/:", scrout[i-1])) X scrout[i++]=SLASH; X strcpy(scrout+i, SCRATCHOUT+3); X#else X sprintf(scrout, SCRATCHOUT, o_directory); X#endif X mktemp(scrout); X cmd_write(from, to, CMD_BANG, 0, scrout); X X /* use those lines as stdin */ X scratch = open(scrout, O_RDONLY); X if (scratch < 0) X { X unlink(scrout); X return -1; X } X } X else X { X scratch = 0; X } X X /* start the filter program */ X fd = rpipe(cmd, scratch); X if (fd < 0) X { X if (to) X { X close(scratch); X unlink(scrout); X } X return -1; X } X X ChangeText X { X /* adjust MARKs for whole lines, and set "new" */ X from &= ~(BLKSIZE - 1); X if (to) X { X to &= ~(BLKSIZE - 1); X to += BLKSIZE; X new = to; X } X else X { X new = from + BLKSIZE; X } X X /* repeatedly read in new text and add it */ X while ((i = tread(fd, tmpblk.c, BLKSIZE - 1)) > 0) X { X tmpblk.c[i] = '\0'; X add(new, tmpblk.c); X for (i = 0; tmpblk.c[i]; i++) X { X if (tmpblk.c[i] == '\n') X { X new = (new & ~(BLKSIZE - 1)) + BLKSIZE; X } X else X { X new++; X } X } X } X } X X /* delete old text, if any */ X if (to) X { X delete(from, to); X } X X /* Reporting... */ X rptlabel = "more"; X if (rptlines < 0) X { X rptlines = -rptlines; X rptlabel = "less"; X } X X /* cleanup */ X rpclose(fd); X if (to) X { X close(scratch); X unlink(scrout); X } X return 0; X} eof if test `wc -c tinytcap.c <<\eof X/* tinytcap.c */ X X/* This file contains functions which simulate the termcap functions, but which X * can only describe the capabilities of the ANSI.SYS and NANSI.SYS drivers on X * an MS-DOS system or the VT-52 emulator of an Atari-ST. These functions X * do *NOT* access a "termcap" database file. X */ X X#include "config.h" X#if MSDOS || TOS || MINIX || COHERENT X X#define CAP(str) CAP2((str)[0], (str)[1]) X#define CAP2(a,b) (((a) << 8) + ((b) & 0xff)) X X#if MSDOS X# define VAL2(v,a) (a) X# define VAL3(v,a,n) (nansi ? (n) : (a)) Xstatic int nansi; X#endif X X#if TOS X# define VAL2(v,a) (v) X# define VAL3(v,a,n) (v) X#endif X X#if MINIX || COHERENT X# define VAL2(v,a) (a) X# define VAL3(v,a,n) (n) X#endif X X X/*ARGSUSED*/ Xint tgetent(bp, name) X char *bp; /* buffer for storing the entry -- ignored */ X char *name; /* name of the entry */ X{ X#if MSDOS X nansi = strcmp(name, "ansi"); X#endif X return 1; X} X Xint tgetnum(id) X char *id; X{ X switch (CAP(id)) X { X case CAP2('l','i'): return 25; X case CAP2('c','o'): return 80; X case CAP2('s','g'): return 0; X case CAP2('u','g'): return 0; X default: return -1; X } X} X Xint tgetflag(id) X char *id; X{ X switch (CAP(id)) X { X#if !MINIX || COHERENT X case CAP2('a','m'): return 1; X#endif X case CAP2('b','s'): return 1; X case CAP2('m','i'): return 1; X default: return 0; X } X} X X/*ARGSUSED*/ Xchar *tgetstr(id, bp) X char *id; X char **bp; /* pointer to pointer to buffer - ignored */ X{ X switch (CAP(id)) X { X case CAP2('c','e'): return VAL2("\033K", "\033[K"); X case CAP2('c','l'): return VAL2("\033E", "\033[2J"); X X case CAP2('a','l'): return VAL3("\033L", (char *)0, "\033[L"); X case CAP2('d','l'): return VAL3("\033M", (char *)0, "\033[M"); X X case CAP2('c','m'): return VAL2("\033Y%i%+ %+ ", "\033[%i%d;%dH"); X case CAP2('d','o'): return VAL2("\033B", "\033[B"); X case CAP2('n','d'): return VAL2("\033C", "\033[C"); X case CAP2('u','p'): return VAL2("\033A", "\033[A"); X case CAP2('t','i'): return VAL2("\033e", ""); X case CAP2('t','e'): return VAL2("", ""); X X case CAP2('s','o'): return VAL2("\033p", "\033[7m"); X case CAP2('s','e'): return VAL2("\033q", "\033[m"); X case CAP2('u','s'): return VAL2((char *)0, "\033[4m"); X case CAP2('u','e'): return VAL2((char *)0, "\033[m"); X case CAP2('m','d'): return VAL2((char *)0, "\033[1m"); X case CAP2('m','e'): return VAL2((char *)0, "\033[m"); X X#if MINIX || COHERENT X case CAP2('k','u'): return "\033[A"; X case CAP2('k','d'): return "\033[B"; X case CAP2('k','l'): return "\033[D"; X case CAP2('k','r'): return "\033[C"; X case CAP2('k','P'): return "\033[V"; X case CAP2('k','N'): return "\033[U"; X case CAP2('k','h'): return "\033[H"; X# if MINIX X case CAP2('k','H'): return "\033[Y"; X# else /* COHERENT */ X case CAP2('k','H'): return "\033[24H"; X# endif X#else /* MS-DOS or TOS */ X case CAP2('k','u'): return "#H"; X case CAP2('k','d'): return "#P"; X case CAP2('k','l'): return "#K"; X case CAP2('k','r'): return "#M"; X case CAP2('k','h'): return "#G"; X case CAP2('k','H'): return "#O"; X case CAP2('k','P'): return "#I"; X case CAP2('k','N'): return "#Q"; X#endif X X default: return (char *)0; X } X} X X/*ARGSUSED*/ Xchar *tgoto(cm, destcol, destrow) X char *cm; /* cursor movement string -- ignored */ X int destcol;/* destination column, 0 - 79 */ X int destrow;/* destination row, 0 - 24 */ X{ X static char buf[30]; X X#if MSDOS || MINIX || COHERENT X sprintf(buf, "\033[%d;%dH", destrow + 1, destcol + 1); X#endif X#if TOS X sprintf(buf, "\033Y%c%c", ' ' + destrow, ' ' + destcol); X#endif X return buf; X} X X/*ARGSUSED*/ Xvoid tputs(cp, affcnt, outfn) X char *cp; /* the string to output */ X int affcnt; /* number of affected lines -- ignored */ X int (*outfn)(); /* the output function */ X{ X while (*cp) X { X (*outfn)(*cp); X cp++; X } X} X#endif eof if test `wc -c tio.c <<\eof X/* tio.c */ X X/* Author: X * Steve Kirkendall X * 14407 SW Teal Blvd. #C X * Beaverton, OR 97005 X * kirkenda@cs.pdx.edu X */ X X X/* This file contains terminal I/O functions */ X X#include "config.h" X#if BSD || COHERENT X# include X#endif X#include X#include "vi.h" X X X/* This function reads in a line from the terminal. */ Xint vgets(prompt, buf, bsize) X char prompt; /* the prompt character, or '\0' for none */ X char *buf; /* buffer into which the string is read */ X int bsize; /* size of the buffer */ X{ X int len; /* how much we've read so far */ X int ch; /* a character from the user */ X int quoted; /* is the next char quoted? */ X int tab; /* column position of cursor */ X char widths[132]; /* widths of characters */ X#ifndef NO_DIGRAPH X int erased; /* 0, or first char of a digraph */ X#endif X X /* show the prompt */ X move(LINES - 1, 0); X tab = 0; X if (prompt) X { X addch(prompt); X tab = 1; X } X clrtoeol(); X refresh(); X X /* read in the line */ X#ifndef NO_DIGRAPH X erased = X#endif X quoted = len = 0; X for (;;) X { X ch = getkey(quoted ? 0 : WHEN_EX); X X /* some special conversions */ X if (ch == ctrl('D') && len == 0) X ch = ctrl('['); X#ifndef NO_DIGRAPH X if (*o_digraph && erased != 0 && ch != '\b') X { X ch = digraph(erased, ch); X erased = 0; X } X#endif X X /* inhibit detection of special chars (except ^J) after a ^V */ X if (quoted && ch != '\n') X { X ch |= 256; X } X X /* process the character */ X switch(ch) X { X case ctrl('V'): X qaddch('^'); X qaddch('\b'); X quoted = TRUE; X break; X X case ctrl('['): X return -1; X X case '\n': X#if OSK X case '\l': X#else X case '\r': X#endif X clrtoeol(); X goto BreakBreak; X X case '\b': X if (len > 0) X { X len--; X#ifndef NO_DIGRAPH X erased = buf[len]; X#endif X for (ch = widths[len]; ch > 0; ch--) X addch('\b'); X if (mode == MODE_EX) X { X clrtoeol(); X } X tab -= widths[len]; X } X else X { X return -1; X } X break; X X default: X /* strip off quotation bit */ X if (ch & 256) X { X ch &= ~256; X quoted = FALSE; X qaddch(' '); X qaddch('\b'); X } X /* add & echo the char */ X if (len < bsize - 1) X { X if (ch == '\t') X { X widths[len] = *o_tabstop - (tab % *o_tabstop); X addstr(" " + 8 - widths[len]); X tab += widths[len]; X } X else if (ch > 0 && ch < ' ') /* > 0 by GB */ X { X addch('^'); X addch(ch + '@'); X widths[len] = 2; X tab += 2; X } X else if (ch == '\177') X { X addch('^'); X addch('?'); X widths[len] = 2; X tab += 2; X } X else X { X addch(ch); X widths[len] = 1; X tab++; X } X buf[len++] = ch; X } X else X { X beep(); X } X } X } XBreakBreak: X refresh(); X buf[len] = '\0'; X return len; X} X X X/* ring the terminal's bell */ Xvoid beep() X{ X if (*o_vbell) X { X do_VB(); X refresh(); X } X else if (*o_errorbells) X { X ttywrite("\007", 1); X } X} X Xstatic int manymsgs; /* This variable keeps msgs from overwriting each other */ Xstatic char pmsg[80]; /* previous message (waiting to be displayed) */ X X Xstatic int showmsg() X{ X /* if there is no message to show, then don't */ X if (!manymsgs) X return FALSE; X X /* display the message */ X move(LINES - 1, 0); X if (*pmsg) X { X standout(); X qaddch(' '); X qaddstr(pmsg); X qaddch(' '); X standend(); X } X clrtoeol(); X X manymsgs = FALSE; X return TRUE; X} X X Xvoid endmsgs() X{ X if (manymsgs) X { X showmsg(); X addch('\n'); X } X} X X/* Write a message in an appropriate way. This should really be a varargs X * function, but there is no such thing as vwprintw. Hack!!! X * X * In MODE_EX or MODE_COLON, the message is written immediately, with a X * newline at the end. X * X * In MODE_VI, the message is stored in a character buffer. It is not X * displayed until getkey() is called. msg() will call getkey() itself, X * if necessary, to prevent messages from being lost. X * X * msg("") - clears the message line X * msg("%s %d", ...) - does a printf onto the message line X */ X/*VARARGS1*/ Xvoid msg(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7) X char *fmt; X long arg1, arg2, arg3, arg4, arg5, arg6, arg7; X{ X if (mode != MODE_VI) X { X sprintf(pmsg, fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7); X qaddstr(pmsg); X addch('\n'); X exrefresh(); X } X else X { X /* wait for keypress between consecutive msgs */ X if (manymsgs) X { X getkey(WHEN_MSG); X } X X /* real message */ X sprintf(pmsg, fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7); X manymsgs = TRUE; X } X} X X X/* This function calls refresh() if the option exrefresh is set */ Xvoid exrefresh() X{ X char *scan; X X /* If this ex command wrote ANYTHING set exwrote so vi's : command X * can tell that it must wait for a user keystroke before redrawing. X */ X for (scan=kbuf; scan 0 && keybuf[atnext]) X { X msg("Can't nest @ commands"); X return FALSE; X } X X /* use the empty portion of keybuf[] to get chars from the cut buffer */ X len = cb2str(cbname, keybuf + nkeys, sizeof keybuf - nkeys); X if (len < 0) X { X msg("Invalid cut buffer name. Must be a-z"); X return FALSE; X } X if (len == 0) X { X msg("Cut buffer \"%c is empty", cbname); X return FALSE; X } X else if (len >= sizeof keybuf - nkeys) X { X msg("Cut buffer \"%c is too large to execute", cbname); X return FALSE; X } X X /* prepare to "read" those keys on subsequent getkey() calls */ X atnext = nkeys; X return TRUE; X} X#endif X X/* This array describes mapped key sequences */ Xstatic struct _keymap X{ X char *name; /* name of the key, or NULL */ X char rawin[LONGKEY]; /* the unmapped version of input */ X char cooked[80]; /* the mapped version of input */ X int len; /* length of the unmapped version */ X int when; /* when is this key mapped? */ X} X mapped[MAXMAPS]; X X#if !MSDOS && !TOS X# if BSD || COHERENT Xstatic jmp_buf env_timeout; Xstatic int dummy() X{ X longjmp(env_timeout, 1); X return 0; X} X# else Xstatic int dummy() X{ X} X# endif X#endif X X/* This function reads in a keystroke for VI mode. It automatically handles X * key mapping. X */ Xint getkey(when) X int when; /* which bits must be ON? */ X{ X static char *cooked; /* rawin, or pointer to converted key */ X static int oldwhen; /* "when" from last time */ X static int oldleft; X static long oldtop; X static long oldnlines; X static char *cshape; /* current cursor shape */ X REG char *kptr; /* &keybuf[next] */ X REG struct _keymap *km; /* used to count through keymap */ X REG int i, j, k; X X#ifdef DEBUG X watch(); X#endif X X /* if this key is needed for delay between multiple error messages, X * then reset the manymsgs flag and abort any mapped key sequence. X */ X if (showmsg()) X { X if (when == WHEN_MSG) X { X qaddstr("[More...]"); X refresh(); X cooked = (char *)0; X } X else if (when == WHEN_VIINP || when == WHEN_VIREP) X { X redraw(cursor, TRUE); X } X } X X#ifndef NO_AT X /* if we're in the middle of a visual @ macro, take atnext */ X if (atnext > 0) X { X if (keybuf[atnext]) X { X return keybuf[atnext++]; X } X atnext = 0; X } X#endif X X /* if we're doing a mapped key, get the next char */ X if (cooked && *cooked) X { X return *cooked++; X } X X /* if keybuf is empty, fill it */ X if (next == nkeys) X { X#ifndef NO_CURSORSHAPE X /* make sure the cursor is the right shape */ X if (has_CQ) X { X cooked = cshape; X switch (when) X { X case WHEN_EX: cooked = CX; break; X case WHEN_VICMD: cooked = CV; break; X case WHEN_VIINP: cooked = CI; break; X case WHEN_VIREP: cooked = CR; break; X } X if (cooked != cshape) X { X cshape = cooked; X switch (when) X { X case WHEN_EX: do_CX(); break; X case WHEN_VICMD: do_CV(); break; X case WHEN_VIINP: do_CI(); break; X case WHEN_VIREP: do_CR(); break; X } X } X cooked = (char *)0; X } X#endif X X#ifndef NO_SHOWMODE X /* if "showmode" then say which mode we're in */ X if (*o_smd X && mode == MODE_VI X && (when != oldwhen || topline != oldtop || leftcol != oldleft || nlines != oldnlines)) X { X oldwhen = when; X oldtop = topline; X oldleft = leftcol; X oldnlines = nlines; X X if (when & WHEN_VICMD) X { X redraw(cursor, FALSE); X move(LINES - 1, COLS - 10); X standout(); X addstr("Command"); X standend(); X redraw(cursor, FALSE); X } X else if (when & WHEN_VIINP) X { X redraw(cursor, TRUE); X move(LINES - 1, COLS - 10); X standout(); X addstr(" Input "); X standend(); X redraw(cursor, TRUE); X } X else if (when & WHEN_VIREP) X { X redraw(cursor, TRUE); X move(LINES - 1, COLS - 10); X standout(); X addstr("Replace"); X standend(); X redraw(cursor, TRUE); X } X } X else X#endif X X /* redraw if getting a VI command */ X if (when & WHEN_VICMD) X { X redraw(cursor, FALSE); X } X X /* read the rawin keystrokes */ X refresh(); X while ((nkeys = ttyread(keybuf, sizeof keybuf)) < 0) X { X /* terminal was probably resized */ X *o_lines = LINES; X *o_columns = COLS; X if (when & (WHEN_VICMD|WHEN_VIINP|WHEN_VIREP)) X { X redraw(MARK_UNSET, FALSE); X redraw(cursor, (when & WHEN_VICMD) == 0); X refresh(); X } X } X next = 0; X X /* if nkeys == 0 then we've reached EOF of an ex script. */ X if (nkeys == 0) X { X tmpabort(TRUE); X move(LINES - 1, 0); X clrtoeol(); X refresh(); X endwin(); X exit(1); X } X } X X /* see how many mapped keys this might be */ X kptr = &keybuf[next]; X for (i = j = 0, k = -1, km = mapped; i < MAXMAPS; i++, km++) X { X if ((km->when & when) && km->len > 0 && *km->rawin == *kptr) X { X if (km->len > nkeys - next) X { X if (!strncmp(km->rawin, kptr, nkeys - next)) X { X j++; X } X } X else X { X if (!strncmp(km->rawin, kptr, km->len)) X { X j++; X k = i; X } X } X } X } X X /* if more than one, try to read some more */ X while (j > 1) X { X#if BSD || COHERENT X if (setjmp(env_timeout)) X { X /* we timed out - assume no mapping */ X j = 0; X break; X } X#endif X#if ANY_UNIX X signal(SIGALRM, dummy); X#endif X#if OSK X signal(SIGQUIT, dummy); X#endif X alarm((unsigned)*o_keytime); X i = nkeys; X if ((k = ttyread(keybuf + nkeys, sizeof keybuf - nkeys)) >= 0) X { X nkeys += k; X } X alarm(0); X#if OSK X# ifndef DEBUG X signal(SIGQUIT, SIG_IGN); X# endif X#endif X X /* if we couldn't read any more, pretend 0 mapped keys */ X if (i == nkeys) X { X j = 0; X } X else /* else we got some more - try again */ X { X for (i = j = 0, k = -1, km = mapped; i < MAXMAPS; i++, km++) X { X if ((km->when & when) && km->len > 0 && *km->rawin == *kptr) X { X if (km->len > nkeys - next) X { X if (!strncmp(km->rawin, kptr, nkeys - next)) X { X j++; X } X } X else X { X if (!strncmp(km->rawin, kptr, km->len)) X { X j++; X k = i; X } X } X } X } X } X } X X /* if unambiguously mapped key, use it! */ X if (j == 1 && k >= 0) X { X next += mapped[k].len; X cooked = mapped[k].cooked; X#ifndef NO_EXTENSIONS X if ((when & (WHEN_VIINP|WHEN_VIREP)) X && (mapped[k].when & WHEN_INMV)) X { X return 0; /* special case, means "a movement char follows" */ X } X else X#endif X { X return *cooked++; X } X } X else X /* assume key is unmapped, but still translate weird erase key to '\b' */ X if (keybuf[next] == ERASEKEY && when != 0) X { X next++; X return '\b'; X } X else if (keybuf[next] == '\0') X { X next++; X return ('A' & 0x1f); X } X else X { X return keybuf[next++]; X } X} X X X/* This function maps or unmaps a key */ Xvoid mapkey(rawin, cooked, when, name) X char *rawin; /* the input key sequence, before mapping */ X char *cooked;/* after mapping */ X short when; /* bitmap of when mapping should happen */ X char *name; /* name of the key, if any */ X{ X int i, j; X X#ifndef NO_EXTENSIONS X /* if the mapped version starts with the word "visual" then set WHEN_INMV */ X if (!strncmp(cooked, "visual ", 7)) X { X when |= WHEN_INMV; X cooked += 7; X } X /* if WHEN_INMV is set, then WHEN_VIINP and WHEN_VIREP must be set */ X if (when & WHEN_INMV) X { X when |= (WHEN_VIINP | WHEN_VIREP); X } X#endif X X /* see if the key sequence was mapped before */ X j = strlen(rawin); X for (i = 0; i < MAXMAPS; i++) X { X if (mapped[i].len == j X && !strncmp(mapped[i].rawin, rawin, j) X && (mapped[i].when & when)) X { X break; X } X } X X /* if not already mapped, then try to find a new slot to use */ X if (i == MAXMAPS) X { X for (i = 0; i < MAXMAPS && mapped[i].len > 0; i++) X { X } X } X X /* no room for the new key? */ X if (i == MAXMAPS) X { X msg("No room left in the key map table"); X return; X } X X /* map the key */ X if (cooked && *cooked) X { X /* Map the key */ X mapped[i].len = j; X strncpy(mapped[i].rawin, rawin, j); X strcpy(mapped[i].cooked, cooked); X mapped[i].when = when; X mapped[i].name = name; X } X else /* unmap the key */ X { X mapped[i].len = 0; X } X} X X/* Dump keys of a given type - WHEN_VICMD dumps the ":map" keys, and X * WHEN_VIINP|WHEN_VIREP dumps the ":map!" keys X */ Xvoid dumpkey(when) X int when; /* WHEN_XXXX of mappings to be dumped */ X{ X int i, len, mlen; X char *scan; X char *mraw; X X for (i = 0; i < MAXMAPS; i++) X { X /* skip unused entries, or entries that don't match "when" */ X if (mapped[i].len <= 0 || !(mapped[i].when & when)) X { X continue; X } X X /* dump the key label, if any */ X len = 8; X if (mapped[i].name) X { X qaddstr(mapped[i].name); X len -= strlen(mapped[i].name); X } X do X { X qaddch(' '); X } while (len-- > 0); X X /* dump the raw version */ X len = 0; X mlen = mapped[i].len; X mraw = mapped[i].rawin; X for (scan = mraw; scan < mraw + mlen; scan++) X { X if (UCHAR(*scan) < ' ' || *scan == '\177') X { X qaddch('^'); X qaddch(*scan ^ '@'); X len += 2; X } X else X { X qaddch(*scan); X len++; X } X } X do X { X qaddch(' '); X } while (++len < 8); X X /* dump the mapped version */ X#ifndef NO_EXTENSIONS X if ((mapped[i].when & WHEN_INMV) && (when & (WHEN_VIINP|WHEN_VIREP))) X { X qaddstr("visual "); X } X#endif X for (scan = mapped[i].cooked; *scan; scan++) X { X if (UCHAR(*scan) < ' ' || *scan == '\177') X { X qaddch('^'); X qaddch(*scan ^ '@'); X } X else X { X qaddch(*scan); X } X } X X addch('\n'); X exrefresh(); X } X} X X X X#ifndef MKEXRC X/* This function saves the current configuration of mapped keys to a file */ Xvoid savekeys(fd) X int fd; /* file descriptor to save them to */ X{ X int i; X char buf[80]; X X /* now write a map command for each key other than the arrows */ X for (i = 0; i < MAXMAPS; i++) X { X /* ignore keys that came from termcap */ X if (mapped[i].name) X { X continue; X } X X /* If this isn't used, ignore it */ X if (mapped[i].len <= 0) X { X continue; X } X X /* write the map command */ X#ifndef NO_EXTENSIONS X if (mapped[i].when & WHEN_INMV) X { X#if OSK X char fmt[80]; X sprintf(fmt, "map%%s %%.%ds %%s\n", mapped[i].len); X sprintf(buf, fmt, X (mapped[i].when & WHEN_VICMD) ? "" : "!", X#else X sprintf(buf, "map%s %.*s visual %s\n", X (mapped[i].when & WHEN_VICMD) ? "" : "!", X mapped[i].len, X#endif X mapped[i].rawin, X mapped[i].cooked); X twrite(fd, buf, strlen(buf)); X } X else X#endif X { X if (mapped[i].when & WHEN_VICMD) X { X#if OSK X char fmt[80]; X sprintf(fmt, "map %%.%ds %%s\n", mapped[i].len); X sprintf(buf, fmt, X#else X sprintf(buf, "map %.*s %s\n", mapped[i].len, X#endif X mapped[i].rawin, X mapped[i].cooked); X twrite(fd, buf, strlen(buf)); X } X if (mapped[i].when & (WHEN_VIINP | WHEN_VIREP)) X { X#if OSK X char fmt[80]; X sprintf(fmt, "map! %%.%ds %%s\n", mapped[i].len); X sprintf(buf, fmt, X#else X sprintf(buf, "map! %.*s %s\n", mapped[i].len, X#endif X mapped[i].rawin, X mapped[i].cooked); X twrite(fd, buf, strlen(buf)); X } X } X } X} X#endif eof if test `wc -c tmp.c <<\eof X/* tmpfile.c */ X X/* Author: X * Steve Kirkendall X * 14407 SW Teal Blvd. #C X * Beaverton, OR 97005 X * kirkenda@cs.pdx.edu X */ X X X/* This file contains functions which create & readback a TMPFILE */ X X X#include "config.h" X#include X#include "vi.h" X#if TOS X# include X#else X# if OSK X# include "osk.h" X# else X# include X# endif X#endif X X X#ifndef NO_MODELINE Xstatic void do_modeline(l, stop) X long l; /* line number to start at */ X long stop; /* line number to stop at */ X{ X char *str; /* used to scan through the line */ X char *start; /* points to the start of the line */ X char buf[80]; X X /* if modelines are disabled, then do nothing */ X if (!*o_modeline) X { X return; X } X X /* for each line... */ X for (l = 1; l <= stop; l++) X { X /* for each position in the line.. */ X for (str = fetchline(l); *str; str++) X { X /* if it is the start of a modeline command... */ X if ((str[0] == 'e' && str[1] == 'x' X || str[0] == 'v' && str[1] == 'i') X && str[2] == ':') X { X start = str += 3; X X /* find the end */ X while (*str && *str != ':') X { X str++; X } X X /* if it is a well-formed modeline, execute it */ X if (*str && str - start < sizeof buf) X { X strncpy(buf, start, (int)(str - start)); X buf[str - start] = '\0'; X doexcmd(buf); X break; X } X } X } X } X} X#endif X X X/* The FAIL() macro prints an error message and then exits. */ X#define FAIL(why,arg) mode = MODE_EX; msg(why, arg); endwin(); exit(9) X X/* This is the name of the temp file */ Xstatic char tmpname[80]; X X/* This function creates the temp file and copies the original file into it. X * Returns if successful, or stops execution if it fails. X */ Xint tmpstart(filename) X char *filename; /* name of the original file */ X{ X int origfd; /* fd used for reading the original file */ X struct stat statb; /* stat buffer, used to examine inode */ X REG BLK *this; /* pointer to the current block buffer */ X REG BLK *next; /* pointer to the next block buffer */ X int inbuf; /* number of characters in a buffer */ X int nread; /* number of bytes read */ X REG int j, k; X int i; X int sum; /* used for calculating a checksum for this */ X char *scan; X long nbytes; X X /* switching to a different file certainly counts as a change */ X changes++; X redraw(MARK_UNSET, FALSE); X X /* open the original file for reading */ X *origname = '\0'; X if (filename && *filename) X { X strcpy(origname, filename); X origfd = open(origname, O_RDONLY); X if (origfd < 0 && errno != ENOENT) X { X msg("Can't open \"%s\"", origname); X return tmpstart(""); X } X if (origfd >= 0) X { X if (stat(origname, &statb) < 0) X { X FAIL("Can't stat \"%s\"", origname); X } X#if TOS X if (origfd >= 0 && (statb.st_mode & S_IJDIR)) X#else X# if OSK X if (origfd >= 0 && (statb.st_mode & S_IFDIR)) X# else X if (origfd >= 0 && (statb.st_mode & S_IFMT) != S_IFREG) X# endif X#endif X { X msg("\"%s\" is not a regular file", origname); X return tmpstart(""); X } X } X else X { X stat(".", &statb); X } X if (origfd >= 0) X { X origtime = statb.st_mtime; X#if MSDOS || OSK X if (*o_readonly || !(statb.st_mode & S_IWRITE)) X#endif X#if TOS X if (*o_readonly || (statb.st_mode & S_IJRON)) X#endif X#if ANY_UNIX X if (*o_readonly || !(statb.st_mode & X (statb.st_uid != geteuid() ? 0022 : 0200))) X#endif X { X setflag(file, READONLY); X } X } X else X { X origtime = 0L; X } X } X else X { X setflag(file, NOFILE); X origfd = -1; X origtime = 0L; X stat(".", &statb); X } X X /* generate a checksum from the file's name */ X for (sum = 0, scan = origname + strlen(origname); X --scan >= origname && (isascii(*scan) && isalnum(*scan) || *scan == '.'); X sum = sum + *scan) X { X } X sum &= 0xf; X X /* make a name for the tmp file */ X#if MSDOS || TOS X /* MS-Dos doesn't allow multiple slashes, but supports drives X * with current directories. X * This relies on TMPNAME beginning with "%s\\"!!!! X */ X strcpy(tmpname, o_directory); X if ((i = strlen(tmpname)) && !strchr(":/\\", tmpname[i-1])) X tmpname[i++]=SLASH; X sprintf(tmpname+i, TMPNAME+3, sum, statb.st_ino, statb.st_dev); X#else X sprintf(tmpname, TMPNAME, o_directory, sum, statb.st_ino, statb.st_dev); X#endif X X /* make sure nobody else is editing the same file */ X if (access(tmpname, 0) == 0) X { X if (*origname) X { X msg("\"%s\" is busy", filename); X return tmpstart(""); X } X FAIL("\"%s\" is busy", filename); X } X X /* create the temp file */ X#if ANY_UNIX X close(creat(tmpname, 0600)); /* only we can read it */ X#else X close(creat(tmpname, FILEPERMS)); /* anybody body can read it, alas */ X#endif X tmpfd = open(tmpname, O_RDWR | O_BINARY); X if (tmpfd < 0) X { X FAIL("Can't create temporary file, errno=%d", errno); X return 1; X } X X /* allocate space for the header in the file */ X write(tmpfd, hdr.c, (unsigned)BLKSIZE); X X#ifndef NO_RECYCLE X /* initialize the block allocator */ X /* This must already be done here, before the first attempt X * to write to the new file! GB */ X garbage(); X#endif X X /* initialize lnum[] */ X for (i = 1; i < MAXBLKS; i++) X { X lnum[i] = INFINITY; X } X lnum[0] = 0; X X /* if there is no original file, then create a 1-line file */ X if (origfd < 0) X { X hdr.n[0] = 0; /* invalid inode# denotes new file */ X X this = blkget(1); /* get the new text block */ X strcpy(this->c, "\n"); /* put a line in it */ X X lnum[1] = 1L; /* block 1 ends with line 1 */ X nlines = 1L; /* there is 1 line in the file */ X nbytes = 1L; X X if (*origname) X { X msg("\"%s\" [NEW FILE] 1 line, 1 char", origname); X } X else X { X msg("\"[NO FILE]\" 1 line, 1 char"); X } X } X else /* there is an original file -- read it in */ X { X hdr.n[0] = statb.st_ino; X nbytes = nlines = 0; X X /* preallocate 1 "next" buffer */ X i = 1; X next = blkget(i); X inbuf = 0; X X /* loop, moving blocks from orig to tmp */ X for (;;) X { X /* "next" buffer becomes "this" buffer */ X this = next; X X /* read [more] text into this block */ X nread = tread(origfd, &this->c[inbuf], BLKSIZE - 1 - inbuf); X if (nread < 0) X { X close(origfd); X close(tmpfd); X tmpfd = -1; X unlink(tmpname); X FAIL("Error reading \"%s\"", origname); X } X X /* convert NUL characters to something else */ X for (k = inbuf; k < inbuf + nread; k++) X { X if (!this->c[k]) X { X setflag(file, HADNUL); X this->c[k] = 0x80; X } X } X inbuf += nread; X X /* if the buffer is empty, quit */ X if (inbuf == 0) X { X goto FoundEOF; X } X X#if MSDOS || TOS X/* BAH! MS text mode read fills inbuf, then compresses eliminating \r X but leaving garbage at end of buf. The same is true for TURBOC. GB. */ X X memset(this->c + inbuf, '\0', BLKSIZE - inbuf); X#endif X X /* search backward for last newline */ X for (k = inbuf; --k >= 0 && this->c[k] != '\n';) X { X } X if (k++ < 0) X { X if (inbuf >= BLKSIZE - 1) X { X k = 80; X } X else X { X k = inbuf; X } X } X X /* allocate next buffer */ X next = blkget(++i); X X /* move fragmentary last line to next buffer */ X inbuf -= k; X for (j = 0; k < BLKSIZE; j++, k++) X { X next->c[j] = this->c[k]; X this->c[k] = 0; X } X X /* if necessary, add a newline to this buf */ X for (k = BLKSIZE - inbuf; --k >= 0 && !this->c[k]; ) X { X } X if (this->c[k] != '\n') X { X setflag(file, ADDEDNL); X this->c[k + 1] = '\n'; X } X X /* count the lines in this block */ X for (k = 0; k < BLKSIZE && this->c[k]; k++) X { X if (this->c[k] == '\n') X { X nlines++; X } X nbytes++; X } X lnum[i - 1] = nlines; X } XFoundEOF: X X /* if this is a zero-length file, add 1 line */ X if (nlines == 0) X { X this = blkget(1); /* get the new text block */ X strcpy(this->c, "\n"); /* put a line in it */ X X lnum[1] = 1; /* block 1 ends with line 1 */ X nlines = 1; /* there is 1 line in the file */ X nbytes = 1; X } X X#if MSDOS || TOS X /* each line has an extra CR that we didn't count yet */ X nbytes += nlines; X#endif X X /* report the number of lines in the file */ X msg("\"%s\" %s %ld line%s, %ld char%s", X origname, X (tstflag(file, READONLY) ? "[READONLY]" : ""), X nlines, X nlines == 1 ? "" : "s", X nbytes, X nbytes == 1 ? "" : "s"); X } X X /* initialize the cursor to start of line 1 */ X cursor = MARK_FIRST; X X /* close the original file */ X close(origfd); X X /* any other messages? */ X if (tstflag(file, HADNUL)) X { X msg("This file contained NULs. They've been changed to \\x80 chars"); X } X if (tstflag(file, ADDEDNL)) X { X msg("Newline characters have been inserted to break up long lines"); X } X X#ifndef NO_MODELINE X if (nlines > 10) X { X do_modeline(1L, 5L); X do_modeline(nlines - 4L, nlines); X } X else X { X do_modeline(1L, nlines); X } X#endif X return 0; X} X X X X/* This function copies the temp file back onto an original file. X * Returns TRUE if successful, or FALSE if the file could NOT be saved. X */ Xint tmpsave(filename, bang) X char *filename; /* the name to save it to */ X int bang; /* forced write? */ X{ X int fd; /* fd of the file we're writing to */ X REG int len; /* length of a text block */ X REG BLK *this; /* a text block */ X long bytes; /* byte counter */ X REG int i; X X /* if no filename is given, assume the original file name */ X if (!filename || !*filename) X { X filename = origname; X } X X /* if still no file name, then fail */ X if (!*filename) X { X msg("Don't know a name for this file -- NOT WRITTEN"); X return FALSE; X } X X /* can't rewrite a READONLY file */ X if (!strcmp(filename, origname) && *o_readonly && !bang) X { X msg("\"%s\" [READONLY] -- NOT WRITTEN", filename); X return FALSE; X } X X /* open the file */ X if (*filename == '>' && filename[1] == '>') X { X filename += 2; X while (*filename == ' ' || *filename == '\t') X { X filename++; X } X#ifdef O_APPEND X fd = open(filename, O_WRONLY|O_APPEND); X#else X fd = open(filename, O_WRONLY); X lseek(fd, 0L, 2); X#endif X } X else X { X /* either the file must not exist, or it must be the original X * file, or we must have a bang X */ X if (strcmp(filename, origname) && access(filename, 0) == 0 && !bang) X { X msg("File already exists - Use :w! to overwrite"); X return FALSE; X } X fd = creat(filename, FILEPERMS); X } X if (fd < 0) X { X msg("Can't write to \"%s\" -- NOT WRITTEN", filename); X return FALSE; X } X X /* write each text block to the file */ X bytes = 0L; X for (i = 1; i < MAXBLKS && (this = blkget(i)) && this->c[0]; i++) X { X for (len = 0; len < BLKSIZE && this->c[len]; len++) X { X } X twrite(fd, this->c, len); X bytes += len; X } X X /* reset the "modified" flag */ X clrflag(file, MODIFIED); X significant = FALSE; X X /* report lines & characters */ X#if MSDOS || TOS X bytes += nlines; /* for the inserted carriage returns */ X#endif X if (strncmp(filename, o_directory, strlen(o_directory))) X { X msg("Wrote \"%s\" %ld lines, %ld characters", filename, nlines, bytes); X } X X /* close the file */ X close(fd); X X return TRUE; X} X X X/* This function deletes the temporary file. If the file has been modified X * and "bang" is FALSE, then it returns FALSE without doing anything; else X * it returns TRUE. X * X * If the "autowrite" option is set, then instead of returning FALSE when X * the file has been modified and "bang" is false, it will call tmpend(). X */ Xint tmpabort(bang) X int bang; X{ X /* if there is no file, return successfully */ X if (tmpfd < 0) X { X return TRUE; X } X X /* see if we must return FALSE -- can't quit */ X if (!bang && tstflag(file, MODIFIED)) X { X /* if "autowrite" is set, then act like tmpend() */ X if (*o_autowrite) X return tmpend(bang); X else X return FALSE; X } X X /* delete the tmp file */ X cutswitch(tmpname); X close(tmpfd); X tmpfd = -1; X unlink(tmpname); X strcpy(prevorig, origname); X prevline = markline(cursor); X *origname = '\0'; X origtime = 0L; X blkinit(); X nlines = 0; X initflags(); X return TRUE; X} X X/* This function saves the file if it has been modified, and then deletes X * the temporary file. Returns TRUE if successful, or FALSE if the file X * needs to be saved but can't be. When it returns FALSE, it will not have X * deleted the tmp file, either. X */ Xint tmpend(bang) X int bang; X{ X /* save the file if it has been modified */ X if (tstflag(file, MODIFIED) && !tmpsave((char *)0, FALSE) && !bang) X { X return FALSE; X } X X /* delete the tmp file */ X tmpabort(TRUE); X X return TRUE; X} X X X/* If the tmp file has been changed, then this function will force those X * changes to be written to the disk, so that the tmp file will survive a X * system crash or power failure. X */ X#if MSDOS || TOS || OSK Xsync() X{ X# if OSK X /* OS9 doesn't need an explicit sync operation, but the linker X * demands something called sync(), so this is a dummy function. X */ X#else X /* MS-DOS and TOS don't flush their buffers until the file is closed, X * so here we close the tmp file and then immediately reopen it. X */ X close(tmpfd); X tmpfd = open(tmpname, O_RDWR | O_BINARY); X#endif X} X#endif eof if test `wc -c