From b140ba75804d71c1daeb45117a8b3393fff6668e Mon Sep 17 00:00:00 2001 From: "William F. Jolitz" Date: Mon, 13 Jul 1992 02:02:27 -0800 Subject: [PATCH] 386BSD 0.1 development Work on file usr/src/usr.bin/elvis/cmd1.c Co-Authored-By: Lynne Greer Jolitz Synthesized-from: 386BSD-0.1 --- usr/src/usr.bin/elvis/cmd1.c | 1779 ++++++++++++++++++++++++++++++++++ 1 file changed, 1779 insertions(+) create mode 100644 usr/src/usr.bin/elvis/cmd1.c diff --git a/usr/src/usr.bin/elvis/cmd1.c b/usr/src/usr.bin/elvis/cmd1.c new file mode 100644 index 0000000000..b322ec4122 --- /dev/null +++ b/usr/src/usr.bin/elvis/cmd1.c @@ -0,0 +1,1779 @@ +/* cmd1.c */ + +/* Author: + * Steve Kirkendall + * 14407 SW Teal Blvd. #C + * Beaverton, OR 97005 + * kirkenda@cs.pdx.edu + */ + + +/* This file contains some of the EX commands - mostly ones that deal with + * files, options, etc. -- anything except text. + */ + +#include "config.h" +#include "ctype.h" +#include "vi.h" +#include "regexp.h" + +#ifdef DEBUG +/* print the selected lines with info on the blocks */ +/*ARGSUSED*/ +void cmd_debug(frommark, tomark, cmd, bang, extra) + MARK frommark; + MARK tomark; + CMD cmd; + int bang; + char *extra; +{ + REG char *scan; + REG long l; + REG int i; + int len; + + /* scan lnum[] to determine which block its in */ + l = markline(frommark); + for (i = 1; l > lnum[i]; i++) + { + } + + do + { + /* fetch text of the block containing that line */ + scan = blkget(i)->c; + + /* calculate its length */ + if (scan[BLKSIZE - 1]) + { + len = BLKSIZE; + } + else + { + len = strlen(scan); + } + + /* print block stats */ + msg("##### hdr[%d]=%d, lnum[%d-1]=%ld, lnum[%d]=%ld (%ld lines)", + i, hdr.n[i], i, lnum[i-1], i, lnum[i], lnum[i] - lnum[i - 1]); + msg("##### len=%d, buf=0x%lx, %sdirty", + len, scan, ((int *)scan)[MAXBLKS + 1] ? "" : "not "); + if (bang) + { + while (--len >= 0) + { + addch(*scan); + scan++; + } + } + exrefresh(); + + /* next block */ + i++; + } while (i < MAXBLKS && lnum[i] && lnum[i - 1] < markline(tomark)); +} + + +/* This function checks a lot of conditions to make sure they aren't screwy */ +/*ARGSUSED*/ +void cmd_validate(frommark, tomark, cmd, bang, extra) + MARK frommark; + MARK tomark; + CMD cmd; + int bang; + char *extra; +{ + char *scan; + int i; + int nlcnt; /* used to count newlines */ + int len; /* counts non-NUL characters */ + + /* check lnum[0] */ + if (lnum[0] != 0L) + { + msg("lnum[0] = %ld", lnum[0]); + } + + /* check each block */ + for (i = 1; lnum[i] <= nlines; i++) + { + scan = blkget(i)->c; + if (scan[BLKSIZE - 1]) + { + msg("block %d has no NUL at the end", i); + } + else + { + for (nlcnt = len = 0; *scan; scan++, len++) + { + if (*scan == '\n') + { + nlcnt++; + } + } + if (scan[-1] != '\n') + { + msg("block %d doesn't end with '\\n' (length %d)", i, len); + } + if (bang || nlcnt != lnum[i] - lnum[i - 1]) + { + msg("block %d (line %ld?) has %d lines, but should have %ld", + i, lnum[i - 1] + 1L, nlcnt, lnum[i] - lnum[i - 1]); + } + } + exrefresh(); + } + + /* check lnum again */ + if (lnum[i] != INFINITY) + { + msg("hdr.n[%d] = %d, but lnum[%d] = %ld", + i, hdr.n[i], i, lnum[i]); + } + + msg("# = \"%s\", %% = \"%s\"", prevorig, origname); + msg("V_from=%ld.%d, cursor=%ld.%d", markline(V_from), markidx(V_from), markline(cursor), markidx(cursor)); +} +#endif /* DEBUG */ + + +/*ARGSUSED*/ +void cmd_mark(frommark, tomark, cmd, bang, extra) + MARK frommark; + MARK tomark; + CMD cmd; + int bang; + char *extra; +{ + /* validate the name of the mark */ + if (*extra == '"') + { + extra++; + } + /* valid mark names are lowercase ascii characters */ + if (!isascii(*extra) || !islower(*extra) || extra[1]) + { + msg("Invalid mark name"); + return; + } + + mark[*extra - 'a'] = tomark; +} + +/*ARGSUSED*/ +void cmd_write(frommark, tomark, cmd, bang, extra) + MARK frommark; + MARK tomark; + CMD cmd; + int bang; + char *extra; +{ + int fd; + int append; /* boolean: write in "append" mode? */ + REG long l; + REG char *scan; + REG int i; + + /* if writing to a filter, then let filter() handle it */ + if (*extra == '!') + { + filter(frommark, tomark, extra + 1, FALSE); + return; + } + + /* if all lines are to be written, use tmpsave() */ + if (frommark == MARK_FIRST && tomark == MARK_LAST && cmd == CMD_WRITE) + { + tmpsave(extra, bang); + return; + } + + /* see if we're going to do this in append mode or not */ + append = FALSE; + if (extra[0] == '>' && extra[1] == '>') + { + extra += 2; + append = TRUE; + } + + /* either the file must not exist, or we must have a ! or be appending */ + if (access(extra, 0) == 0 && !bang && !append) + { + msg("File already exists - Use :w! to overwrite"); + return; + } + + /* else do it line-by-line, like cmd_print() */ + if (append) + { +#ifdef O_APPEND + fd = open(extra, O_WRONLY|O_APPEND); +#else + fd = open(extra, O_WRONLY); + if (fd >= 0) + { + lseek(fd, 0L, 2); + } +#endif + } + else + { + fd = -1; /* so we know the file isn't open yet */ + } + + if (fd < 0) + { + fd = creat(extra, FILEPERMS); + if (fd < 0) + { + msg("Can't write to \"%s\"", extra); + return; + } + } + for (l = markline(frommark); l <= markline(tomark); l++) + { + /* get the next line */ + scan = fetchline(l); + i = strlen(scan); + scan[i++] = '\n'; + + /* print the line */ + if (twrite(fd, scan, i) < i) + { + msg("Write failed"); + break; + } + } + rptlines = markline(tomark) - markline(frommark) + 1; + rptlabel = "written"; + close(fd); +} + + +/*ARGSUSED*/ +void cmd_shell(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + static char prevextra[80]; + + /* special case: ":sh" means ":!sh" */ + if (cmd == CMD_SHELL) + { + extra = o_shell; + frommark = tomark = 0L; + } + + /* if extra is "!", substitute previous command */ + if (*extra == '!') + { + if (!*prevextra) + { + msg("No previous shell command to substitute for '!'"); + return; + } + extra = prevextra; + } + else if (cmd == CMD_BANG && strlen(extra) < sizeof(prevextra) - 1) + { + strcpy(prevextra, extra); + } + + /* warn the user if the file hasn't been saved yet */ + if (*o_warn && tstflag(file, MODIFIED)) + { + if (mode == MODE_VI) + { + mode = MODE_COLON; + } + msg("Warning: \"%s\" has been modified but not yet saved", origname); + } + + /* if no lines were specified, just run the command */ + suspend_curses(); + if (frommark == 0L) + { + system(extra); + } + else /* pipe lines from the file through the command */ + { + filter(frommark, tomark, extra, TRUE); + } + + /* resume curses quietly for MODE_EX, but noisily otherwise */ + resume_curses(mode == MODE_EX); +} + + +/*ARGSUSED*/ +void cmd_global(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; /* rest of the command line */ +{ + char *cmdptr; /* the command from the command line */ + char cmdln[100]; /* copy of the command from the command line */ + char *line; /* a line from the file */ + long l; /* used as a counter to move through lines */ + long lqty; /* quantity of lines to be scanned */ + long nchanged; /* number of lines changed */ + regexp *re; /* the compiled search expression */ + + /* can't nest global commands */ + if (doingglobal) + { + msg("Can't nest global commands."); + rptlines = -1L; + return; + } + + /* ":g! ..." is the same as ":v ..." */ + if (bang) + { + cmd = CMD_VGLOBAL; + } + + /* make sure we got a search pattern */ + if (*extra != '/' && *extra != '?') + { + msg("Usage: %c /regular expression/ command", cmd == CMD_GLOBAL ? 'g' : 'v'); + return; + } + + /* parse & compile the search pattern */ + cmdptr = parseptrn(extra); + if (!extra[1]) + { + msg("Can't use empty regular expression with '%c' command", cmd == CMD_GLOBAL ? 'g' : 'v'); + return; + } + re = regcomp(extra + 1); + if (!re) + { + /* regcomp found & described an error */ + return; + } + + /* for each line in the range */ + doingglobal = TRUE; + ChangeText + { + /* NOTE: we have to go through the lines in a forward order, + * otherwise "g/re/p" would look funny. *BUT* for "g/re/d" + * to work, simply adding 1 to the line# on each loop won't + * work. The solution: count lines relative to the end of + * the file. Think about it. + */ + for (l = nlines - markline(frommark), + lqty = markline(tomark) - markline(frommark) + 1L, + nchanged = 0L; + lqty > 0 && nlines - l >= 0 && nchanged >= 0L; + l--, lqty--) + { + /* fetch the line */ + line = fetchline(nlines - l); + + /* if it contains the search pattern... */ + if ((!regexec(re, line, 1)) == (cmd != CMD_GLOBAL)) + { + /* move the cursor to that line */ + cursor = MARK_AT_LINE(nlines - l); + + /* do the ex command (without mucking up + * the original copy of the command line) + */ + strcpy(cmdln, cmdptr); + rptlines = 0L; + doexcmd(cmdln); + nchanged += rptlines; + } + } + } + doingglobal = FALSE; + + /* free the regexp */ + free(re); + + /* Reporting...*/ + rptlines = nchanged; +} + + +/*ARGSUSED*/ +void cmd_file(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ +#ifndef CRUNCH + /* if we're given a new filename, use it as this file's name */ + if (extra && *extra) + { + strcpy(origname, extra); + storename(origname); + setflag(file, NOTEDITED); + } +#endif + if (cmd == CMD_FILE) + { +#ifndef CRUNCH + msg("\"%s\" %s%s%s %ld lines, line %ld [%ld%%]", +#else + msg("\"%s\" %s%s %ld lines, line %ld [%ld%%]", +#endif + *origname ? origname : "[NO FILE]", + tstflag(file, MODIFIED) ? "[MODIFIED]" : "", +#ifndef CRUNCH + tstflag(file, NOTEDITED) ?"[NOT EDITED]":"", +#endif + tstflag(file, READONLY) ? "[READONLY]" : "", + nlines, + markline(frommark), + markline(frommark) * 100 / nlines); + } +#ifndef CRUNCH + else if (markline(frommark) != markline(tomark)) + { + msg("range \"%ld,%ld\" contains %ld lines", + markline(frommark), + markline(tomark), + markline(tomark) - markline(frommark) + 1L); + } +#endif + else + { + msg("%ld", markline(frommark)); + } +} + + +/*ARGSUSED*/ +void cmd_edit(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + long line = 1L; /* might be set to prevline */ +#ifndef CRUNCH + char *init = (char *)0; +#endif + + + /* if ":vi", then switch to visual mode, and if no file is named + * then don't switch files. + */ + if (cmd == CMD_VISUAL) + { + mode = MODE_VI; + msg(""); + if (!*extra) + { + return; + } + } + + /* Editing previous file? Then start at previous line */ + if (!strcmp(extra, prevorig)) + { + line = prevline; + } + +#ifndef CRUNCH + /* if we were given an explicit starting line, then start there */ + if (*extra == '+') + { + for (init = ++extra; !isspace(*extra); extra++) + { + } + while (isspace(*extra)) + { + *extra++ = '\0'; + } + if (!*init) + { + init = "$"; + } + if (!extra) + { + extra = origname; + } + } +#endif /* not CRUNCH */ + + /* switch files */ + if (tmpabort(bang)) + { + tmpstart(extra); + if (line <= nlines && line >= 1L) + { + cursor = MARK_AT_LINE(line); + } +#ifndef CRUNCH + if (init) + { + doexcmd(init); + } +#endif + } + else + { + msg("Use edit! to abort changes, or w to save changes"); + + /* so we can say ":e!#" next time... */ + strcpy(prevorig, extra); + prevline = 1L; + } +} + +/* This code is also used for rewind -- GB */ + +/*ARGSUSED*/ +void cmd_next(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + int i, j; + char *scan; + + /* if extra stuff given, use ":args" to define a new args list */ + if (cmd == CMD_NEXT && extra && *extra) + { + cmd_args(frommark, tomark, cmd, bang, extra); + } + + /* move to the next arg */ + if (cmd == CMD_NEXT) + { + i = argno + 1; + } + else if (cmd == CMD_PREVIOUS) + { + i = argno - 1; + } + else /* cmd == CMD_REWIND */ + { + i = 0; + } + if (i < 0 || i >= nargs) + { + msg("No %sfiles to edit", cmd == CMD_REWIND ? "" : "more "); + return; + } + + /* find & isolate the name of the file to edit */ + for (j = i, scan = args; j > 0; j--) + { + while(*scan++) + { + } + } + + /* switch to the next file */ + if (tmpabort(bang)) + { + tmpstart(scan); + argno = i; + } + else + { + msg("Use :%s! to abort changes, or w to save changes", + cmd == CMD_NEXT ? "next" : + cmd == CMD_PREVIOUS ? "previous" : + "rewind"); + } +} + +/* also called from :wq -- always writes back in this case */ + +/*ARGSUSED*/ +void cmd_xit(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + static long whenwarned; /* when the user was last warned of extra files */ + int oldflag; + + /* if there are more files to edit, then warn user */ + if (argno >= 0 && argno + 1 < nargs && whenwarned != changes && (!bang || cmd != CMD_QUIT)) + { + msg("More files to edit -- Use \":n\" to go to next file"); + whenwarned = changes; + return; + } + + if (cmd == CMD_QUIT) + { + oldflag = *o_autowrite; + *o_autowrite = FALSE; + if (tmpabort(bang)) + { + mode = MODE_QUIT; + } + else + { + msg("Use q! to abort changes, or wq to save changes"); + } + *o_autowrite = oldflag; + } + else + { + /* else try to save this file */ + oldflag = tstflag(file, MODIFIED); + if (cmd == CMD_WQUIT) + setflag(file, MODIFIED); + if (tmpend(bang)) + { + mode = MODE_QUIT; + } + else + { + msg("Could not save file -- use quit! to abort changes, or w filename"); + } + if (!oldflag) + clrflag(file, MODIFIED); + } +} + + +/*ARGSUSED*/ +void cmd_args(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + char *scan; + int col; + int arg; + int scrolled = FALSE; + int width; + + /* if no extra names given, or just current name, then report the args + * we have now. + */ + if (!extra || !*extra) + { + /* empty args list? */ + if (nargs == 1 && !*args) + { + return; + } + + /* list the arguments */ + for (scan = args, col = arg = 0; + arg < nargs; + scan += width + 1, col += width, arg++) + { + width = strlen(scan); + if (col + width >= COLS - 4) + { + addch('\n'); + col = 0; + scrolled = TRUE; + } + else if (col > 0) + { + addch(' '); + col++; + } + if (arg == argno) + { + addch('['); + addstr(scan); + addch(']'); + col += 2; + } + else + { + addstr(scan); + } + } + + /* write a trailing newline */ + if ((mode == MODE_EX || mode == MODE_COLON || scrolled) && col) + { + addch('\n'); + } + exrefresh(); + } + else /* new args list given */ + { + for (scan = args, nargs = 1; *extra; ) + { + if (isspace(*extra)) + { + *scan++ = '\0'; + while (isspace(*extra)) + { + extra++; + } + if (*extra) + { + nargs++; + } + } + else + { + *scan++ = *extra++; + } + } + *scan = '\0'; + + /* reset argno to before the first, so :next will go to first */ + argno = -1; + + if (nargs != 1) + { + msg("%d files to edit", nargs); + } + } +} + + +/*ARGSUSED*/ +void cmd_cd(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + char *getenv(); + +#ifndef CRUNCH + /* if current file is modified, and no '!' was given, then error */ + if (tstflag(file, MODIFIED) && !bang) + { + msg("File modified; use \"cd! %s\" to switch anyway", extra); + } +#endif + + /* default directory name is $HOME */ + if (!*extra) + { + extra = getenv("HOME"); + if (!extra) + { + msg("environment variable $HOME not set"); + return; + } + } + + /* go to the directory */ + if (chdir(extra) < 0) + { + perror(extra); + } +} + + +/*ARGSUSED*/ +void cmd_map(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + char *mapto; + char *build, *scan; +#ifndef NO_FKEY + static char *fnames[NFKEYS] = + { + "#10", "#1", "#2", "#3", "#4", + "#5", "#6", "#7", "#8", "#9", +# ifndef NO_SHIFT_FKEY + "#10s", "#1s", "#2s", "#3s", "#4s", + "#5s", "#6s", "#7s", "#8s", "#9s", +# ifndef NO_CTRL_FKEY + "#10c", "#1c", "#2c", "#3c", "#4c", + "#5c", "#6c", "#7c", "#8c", "#9c", +# ifndef NO_ALT_FKEY + "#10a", "#1a", "#2a", "#3a", "#4a", + "#5a", "#6a", "#7a", "#8a", "#9a", +# endif +# endif +# endif + }; + int key; +#endif + + /* "map" with no extra will dump the map table contents */ + if (!*extra) + { +#ifndef NO_ABBR + if (cmd == CMD_ABBR) + { + dumpkey(bang ? WHEN_EX|WHEN_VIINP|WHEN_VIREP : WHEN_VIINP|WHEN_VIREP, TRUE); + } + else +#endif + { + dumpkey(bang ? WHEN_VIINP|WHEN_VIREP : WHEN_VICMD, FALSE); + } + } + else + { + /* "extra" is key to map, followed by what it maps to */ + + /* handle quoting inside the "raw" string */ + for (build = mapto = extra; + *mapto && (*mapto != ' ' && *mapto != '\t'); + *build++ = *mapto++) + { + if (*mapto == ctrl('V') && mapto[1]) + { + mapto++; + } + } + + /* skip whitespace, and mark the end of the "raw" string */ + while ((*mapto == ' ' || *mapto == '\t')) + { + *mapto++ = '\0'; + } + *build = '\0'; + + /* strip ^Vs from the "cooked" string */ + for (scan = build = mapto; *scan; *build++ = *scan++) + { + if (*scan == ctrl('V') && scan[1]) + { + scan++; + } + } + *build = '\0'; + +#ifndef NO_FKEY + /* if the mapped string is '#' and a number, then assume + * the user wanted that function key + */ + if (extra[0] == '#' && isdigit(extra[1])) + { + key = atoi(extra + 1) % 10; +# ifndef NO_SHIFT_FKEY + build = extra + strlen(extra) - 1; + if (*build == 's') + key += 10; +# ifndef NO_CTRL_FKEY + else if (*build == 'c') + key += 20; +# ifndef NO_ALT_FKEY + else if (*build == 'a') + key += 30; +# endif +# endif +# endif + if (FKEY[key]) + mapkey(FKEY[key], mapto, bang ? WHEN_VIINP|WHEN_VIREP : WHEN_VICMD, fnames[key]); + else + msg("This terminal has no %s key", fnames[key]); + } + else +#endif +#ifndef NO_ABBR + if (cmd == CMD_ABBR || cmd == CMD_UNABBR) + { + mapkey(extra, mapto, bang ? WHEN_EX|WHEN_VIINP|WHEN_VIREP : WHEN_VIINP|WHEN_VIREP, "abbr"); + } + else +#endif + { + mapkey(extra, mapto, bang ? WHEN_VIINP|WHEN_VIREP : WHEN_VICMD, (char *)0); + } + } +} + + +/*ARGSUSED*/ +void cmd_set(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + if (!*extra) + { + dumpopts(FALSE);/* "FALSE" means "don't dump all" - only set */ + } + else if (!strcmp(extra, "all")) + { + dumpopts(TRUE); /* "TRUE" means "dump all" - even unset vars */ + } + else + { + setopts(extra); + + /* That option may have affected the appearence of text */ + changes++; + } +} + +/*ARGSUSED*/ +void cmd_tag(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + int fd; /* file descriptor used to read the file */ + char *scan; /* used to scan through the tmpblk.c */ +#ifdef INTERNAL_TAGS + char *cmp; /* char of tag name we're comparing, or NULL */ + char *end; /* marks the end of chars in tmpblk.c */ +#else + int i; +#endif +#ifndef NO_MAGIC + char wasmagic; /* preserves the original state of o_magic */ +#endif + static char prevtag[30]; + + /* if no tag is given, use the previous tag */ + if (!extra || !*extra) + { + if (!*prevtag) + { + msg("No previous tag"); + return; + } + extra = prevtag; + } + else + { + strncpy(prevtag, extra, sizeof prevtag); + prevtag[sizeof prevtag - 1] = '\0'; + } + +#ifndef INTERNAL_TAGS + /* use "ref" to look up the tag info for this tag */ + sprintf(tmpblk.c, "ref -t %s%s %s", (*origname ? "-f" : ""),origname, prevtag); + fd = rpipe(tmpblk.c, 0); + if (fd < 0) + { + msg("Can't run \"%s\"", tmpblk.c); + return; + } + + /* try to read the tag info */ + for (scan = tmpblk.c; + (i = tread(fd, scan, scan - tmpblk.c + BLKSIZE)) > 0; + scan += i) + { + } + *scan = '\0'; + + /* close the pipe. abort if error */ + if (rpclose(fd) != 0 || scan < tmpblk.c + 3) + { + msg("tag \"%s\" not found", extra); + return; + } + +#else /* use internal code to look up the tag */ + /* open the tags file */ + fd = open(TAGS, O_RDONLY); + if (fd < 0) + { + msg("No tags file"); + return; + } + + /* Hmmm... this would have been a lot easier with */ + + /* find the line with our tag in it */ + for(scan = end = tmpblk.c, cmp = extra; ; scan++) + { + /* read a block, if necessary */ + if (scan >= end) + { + end = tmpblk.c + tread(fd, tmpblk.c, BLKSIZE); + scan = tmpblk.c; + if (scan >= end) + { + msg("tag \"%s\" not found", extra); + close(fd); + return; + } + } + + /* if we're comparing, compare... */ + if (cmp) + { + /* matched??? wow! */ + if (!*cmp && *scan == '\t') + { + break; + } + if (*cmp++ != *scan) + { + /* failed! skip to newline */ + cmp = (char *)0; + } + } + + /* if we're skipping to newline, do it fast! */ + if (!cmp) + { + while (scan < end && *scan != '\n') + { + scan++; + } + if (scan < end) + { + cmp = extra; + } + } + } + + /* found it! get the rest of the line into memory */ + for (cmp = tmpblk.c, scan++; scan < end && *scan != '\n'; ) + { + *cmp++ = *scan++; + } + if (scan == end) + { + tread(fd, cmp, BLKSIZE - (int)(cmp - tmpblk.c)); + } + else + *cmp = *scan; + + /* we can close the tags file now */ + close(fd); +#endif /* INTERNAL_TAGS */ + + /* extract the filename from the line, and edit the file */ + for (scan = tmpblk.c; *scan != '\t'; scan++) + { + } + *scan++ = '\0'; + if (strcmp(origname, tmpblk.c) != 0) + { + if (!tmpabort(bang)) + { + msg("Use :tag! to abort changes, or :w to save changes"); + return; + } + tmpstart(tmpblk.c); + } + + /* move to the desired line (or to line 1 if that fails) */ +#ifndef NO_MAGIC + wasmagic = *o_magic; + *o_magic = FALSE; +#endif + cursor = MARK_FIRST; + linespec(scan, &cursor); + if (cursor == MARK_UNSET) + { + cursor = MARK_FIRST; + msg("Tag's address is out of date"); + } +#ifndef NO_MAGIC + *o_magic = wasmagic; +#endif +} + + + + + +/* describe this version of the program */ +/*ARGSUSED*/ +void cmd_version(frommark, tomark, cmd, bang, extra) + MARK frommark; + MARK tomark; + CMD cmd; + int bang; + char *extra; +{ + msg("%s", VERSION); +#ifdef CREDIT + msg("%s", CREDIT); +#endif +#ifdef CREDIT2 + msg("%s", CREDIT2); +#endif +#ifdef COMPILED_BY + msg("Compiled by %s", COMPILED_BY); +#endif +#ifdef COPYING + msg("%s", COPYING); +#endif +} + + +#ifndef NO_MKEXRC +/* make a .exrc file which describes the current configuration */ +/*ARGSUSED*/ +void cmd_mkexrc(frommark, tomark, cmd, bang, extra) + MARK frommark; + MARK tomark; + CMD cmd; + int bang; + char *extra; +{ + int fd; + + /* the default name for the .exrc file EXRC */ + if (!*extra) + { + extra = EXRC; + } + + /* create the .exrc file */ + fd = creat(extra, FILEPERMS); + if (fd < 0) + { + msg("Couldn't create a new \"%s\" file", extra); + return; + } + + /* save stuff */ + saveopts(fd); + savemaps(fd, FALSE); +#ifndef NO_ABBR + savemaps(fd, TRUE); +#endif +#ifndef NO_DIGRAPH + savedigs(fd); +#endif +#ifndef NO_COLOR + savecolor(fd); +#endif + + /* close the file */ + close(fd); + msg("Configuration saved"); +} +#endif + +#ifndef NO_DIGRAPH +/*ARGSUSED*/ +void cmd_digraph(frommark, tomark, cmd, bang, extra) + MARK frommark; + MARK tomark; + CMD cmd; + int bang; + char *extra; +{ + do_digraph(bang, extra); +} +#endif + + +#ifndef NO_ERRLIST +static char errfile[256]; /* the name of a file containing an error */ +static long errline; /* the line number for an error */ +static int errfd = -2; /* fd of the errlist file */ + +/* This static function tries to parse an error message. + * + * For most compilers, the first word is taken to be the name of the erroneous + * file, and the first number after that is taken to be the line number where + * the error was detected. The description of the error follows, possibly + * preceded by an "error ... :" or "warning ... :" label which is skipped. + * + * For Coherent, error messages look like "line#: filename: message". + * + * For non-error lines, or unparsable error lines, this function returns NULL. + * Normally, though, it alters errfile and errline, and returns a pointer to + * the description. + */ +static char *parse_errmsg(text) + REG char *text; +{ + REG char *cpy; + long atol(); +# if COHERENT || TOS /* any Mark Williams compiler */ + /* Get the line number. If no line number, then ignore this line. */ + errline = atol(text); + if (errline == 0L) + return (char *)0; + + /* Skip to the start of the filename */ + while (*text && *text++ != ':') + { + } + if (!*text++) + return (char *)0; + + /* copy the filename to errfile */ + for (cpy = errfile; *text && (*cpy++ = *text++) != ':'; ) + { + } + if (!*text++) + return (char *)0; + cpy[-1] = '\0'; + + return text; +# else /* not a Mark Williams compiler */ + char *errmsg; + + /* the error message is the whole line, by default */ + errmsg = text; + + /* skip leading garbage */ + while (*text && !isalnum(*text)) + { + text++; + } + + /* copy over the filename */ + cpy = errfile; + while(isalnum(*text) || *text == '.') + { + *cpy++ = *text++; + } + *cpy = '\0'; + + /* ignore the name "Error" and filenames that contain a '/' */ + if (*text == '/' || !*errfile || !strcmp(errfile + 1, "rror") || access(errfile, 0) < 0) + { + return (char *)0; + } + + /* skip garbage between filename and line number */ + while (*text && !isdigit(*text)) + { + text++; + } + + /* if the number is part of a larger word, then ignore this line */ + if (*text && isalpha(text[-1])) + { + return (char *)0; + } + + /* get the error line */ + errline = 0L; + while (isdigit(*text)) + { + errline *= 10; + errline += (*text - '0'); + text++; + } + + /* any line which lacks a filename or line number should be ignored */ + if (!errfile[0] || !errline) + { + return (char *)0; + } + + /* locate the beginning of the error description */ + while (*text && !isspace(*text)) + { + text++; + } + while (*text) + { +# ifndef CRUNCH + /* skip "error #:" and "warning #:" clauses */ + if (!strncmp(text + 1, "rror ", 5) + || !strncmp(text + 1, "arning ", 7) + || !strncmp(text + 1, "atal error", 10)) + { + do + { + text++; + } while (*text && *text != ':'); + continue; + } +# endif + + /* anything other than whitespace or a colon is important */ + if (!isspace(*text) && *text != ':') + { + errmsg = text; + break; + } + + /* else keep looking... */ + text++; + } + + return errmsg; +# endif /* not COHERENT */ +} + +/*ARGSUSED*/ +void cmd_errlist(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + static long endline;/* original number of lines in this file */ + static long offset; /* offset of the next line in the errlist file */ + int i; + char *errmsg; + + /* if a new errlist file is named, open it */ + if (extra && extra[0]) + { + /* close the old one */ + if (errfd >= 0) + { + close(errfd); + } + + /* open the new one */ + errfd = open(extra, O_RDONLY); + offset = 0L; + endline = nlines; + } + else if (errfd < 0) + { + /* open the default file */ + errfd = open(ERRLIST, O_RDONLY); + offset = 0L; + endline = nlines; + } + + /* do we have an errlist file now? */ + if (errfd < 0) + { + msg("There is no errlist file"); + beep(); + return; + } + + /* find the next error message in the file */ + do + { + /* read the next line from the errlist */ + lseek(errfd, offset, 0); + if (tread(errfd, tmpblk.c, (unsigned)BLKSIZE) <= 0) + { + msg("No more errors"); + beep(); + close(errfd); + errfd = -2; + return; + } + for (i = 0; tmpblk.c[i] != '\n'; i++) + { + } + tmpblk.c[i++] = 0; + + /* look for an error message in the line */ + errmsg = parse_errmsg(tmpblk.c); + if (!errmsg) + { + offset += i; + } + + } while (!errmsg); + + /* switch to the file containing the error, if this isn't it */ + if (strcmp(origname, errfile)) + { + if (!tmpabort(bang)) + { + msg("Use :er! to abort changes, or :w to save changes"); + beep(); + return; + } + tmpstart(errfile); + endline = nlines; + } + else if (endline == 0L) + { + endline = nlines; + } + + /* go to the line where the error was detected */ + cursor = MARK_AT_LINE(errline + (nlines - endline)); + if (cursor > MARK_LAST) + { + cursor = MARK_LAST; + } + if (mode == MODE_VI) + { + redraw(cursor, FALSE); + } + + /* display the error message */ +#ifdef CRUNCH + msg("%.70s", errmsg); +#else + if (nlines > endline) + { + msg("line %ld(+%ld): %.60s", errline, nlines - endline, errmsg); + } + else if (nlines < endline) + { + msg("line %ld(-%ld): %.60s", errline, endline - nlines, errmsg); + } + else + { + msg("line %ld: %.65s", errline, errmsg); + } +#endif + + /* remember where the NEXT error line will start */ + offset += i; +} + + +/*ARGSUSED*/ +void cmd_make(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + BLK buf; + + /* if the file hasn't been saved, then complain unless ! */ + if (tstflag(file, MODIFIED) && !bang) + { + msg("\"%s\" not saved yet", origname); + return; + } + + /* build the command */ + sprintf(buf.c, "%s %s %s%s", (cmd == CMD_CC ? o_cc : o_make), extra, REDIRECT, ERRLIST); + qaddstr(buf.c); + addch('\n'); + + /* close the old errlist file, if any */ + if (errfd >= 0) + { + close(errfd); + errfd = -3; + } + + /* run the command, with curses temporarily disabled */ + suspend_curses(); + system(buf.c); + resume_curses(mode == MODE_EX); + if (mode == MODE_COLON) + mode = MODE_VI; + + /* run the "errlist" command */ + cmd_errlist(MARK_UNSET, MARK_UNSET, cmd, bang, ERRLIST); +} +#endif + + + +#ifndef NO_COLOR + +/* figure out the number of text colors we use with this configuration */ +# ifndef NO_POPUP +# ifndef NO_VISIBLE +# define NCOLORS 7 +# else +# define NCOLORS 6 +# endif +# else +# ifndef NO_VISIBLE +# define NCOLORS 6 +# else +# define NCOLORS 5 +# endif +# endif + +/* the attribute bytes used in each of "when"s */ +static char bytes[NCOLORS]; + +static struct +{ + char *word; /* a legal word */ + int type; /* what type of word this is */ + int val; /* some other value */ +} + words[] = +{ + {"normal", 1, A_NORMAL}, /* all "when" names must come */ + {"standout", 1, A_STANDOUT}, /* at the top of the list. */ + {"bold", 1, A_BOLD}, /* The first 3 must be normal,*/ + {"underlined", 1, A_UNDERLINE}, /* standout, and bold; the */ + {"italics", 1, A_ALTCHARSET}, /* remaining names follow. */ +#ifndef NO_POPUP + {"popup", 1, A_POPUP}, +#endif +#ifndef NO_VISIBLE + {"visible", 1, A_VISIBLE}, +#endif + + {"black", 3, 0x00}, /* The color names start right*/ + {"blue", 3, 0x01}, /* after the "when" names. */ + {"green", 3, 0x02}, + {"cyan", 3, 0x03}, + {"red", 3, 0x04}, + {"magenta", 3, 0x05}, + {"brown", 3, 0x06}, + {"white", 3, 0x07}, + {"yellow", 3, 0x0E}, /* bright brown */ + {"gray", 3, 0x08}, /* bright black? of course! */ + {"grey", 3, 0x08}, + + {"bright", 2, 0x08}, + {"light", 2, 0x08}, + {"blinking", 2, 0x80}, + {"on", 0, 0}, + {"n", 1, A_NORMAL}, + {"s", 1, A_STANDOUT}, + {"b", 1, A_BOLD}, + {"u", 1, A_UNDERLINE}, + {"i", 1, A_ALTCHARSET}, +#ifndef NO_POPUP + {"p", 1, A_POPUP}, + {"menu", 1, A_POPUP}, +#endif +#ifndef NO_VISIBLE + {"v", 1, A_VISIBLE}, +#endif + {(char *)0, 0, 0} +}; + +/*ARGSUSED*/ +void cmd_color(frommark, tomark, cmd, bang, extra) + MARK frommark, tomark; + CMD cmd; + int bang; + char *extra; +{ + int attrbyte; + int cmode; + int nowbg; /* BOOLEAN: is the next color background? */ + + REG char *scan; + REG i; + + +#ifndef CRUNCH + /* if no args are given, then report the current colors */ + if (!*extra) + { + /* if no colors are set, then say so */ + if (!bytes[0]) + { + msg("no colors have been set"); + return; + } + + /* report all five color combinations */ + for (i = 0; i < NCOLORS; i++) + { + qaddstr("color "); + qaddstr(words[i].word); + qaddch(' '); + if (bytes[i] & 0x80) + qaddstr("blinking "); + switch (bytes[i] & 0xf) + { + case 0x08: qaddstr("gray"); break; + case 0x0e: qaddstr("yellow"); break; + case 0x0f: qaddstr("bright white");break; + default: + if (bytes[i] & 0x08) + qaddstr("light "); + qaddstr(words[(bytes[i] & 0x07) + NCOLORS].word); + } + qaddstr(" on "); + qaddstr(words[((bytes[i] >> 4) & 0x07) + NCOLORS].word); + addch('\n'); + exrefresh(); + } + return; + } +#endif + + /* The default background color is the same as "normal" chars. + * There is no default foreground color. + */ + cmode = A_NORMAL; + attrbyte = bytes[0] & 0x70; + nowbg = FALSE; + + /* parse each word in the "extra" text */ + for (scan = extra; *extra; extra = scan) + { + /* locate the end of the word */ + while (*scan && *scan != ' ') + { + scan++; + } + + /* skip whitespace at the end of the word */ + while(*scan == ' ') + { + *scan++ = '\0'; + } + + /* lookup the word */ + for (i = 0; words[i].word && strcmp(words[i].word, extra); i++) + { + } + + /* if not a word, then complain */ + if (!words[i].word) + { + msg("Invalid color name: %s", extra); + return; + } + + /* process the word */ + switch (words[i].type) + { + case 1: + cmode = words[i].val; + break; + + case 2: + attrbyte |= words[i].val; + break; + + case 3: + if (nowbg) + attrbyte = ((attrbyte & ~0x70) | ((words[i].val & 0x07) << 4)); + else + attrbyte |= words[i].val; + nowbg = TRUE; + break; + } + } + + /* if nowbg isn't set now, then we were never given a foreground color */ + if (!nowbg) + { + msg("usage: color [when] [\"bright\"] [\"blinking\"] foreground [background]"); + return; + } + + /* the first ":color" command MUST define the "normal" colors */ + if (!bytes[0]) + cmode = A_NORMAL; + + /* we should now have a cmode and an attribute byte... */ + + /* set the color */ + setcolor(cmode, attrbyte); + + /* remember what we just did */ + bytes[cmode] = attrbyte; + + /* if the other colors haven't been set yet, then set them to defaults */ + if (!bytes[1]) + { + /* standout is the opposite of normal */ + bytes[1] = ((attrbyte << 4) & 0x70 | (attrbyte >> 4) & 0x07); + setcolor(A_STANDOUT, bytes[1]); + + /* if "normal" isn't bright, then bold defaults to normal+bright + * else bold defaults to bright white. + */ + bytes[2] = attrbyte | ((attrbyte & 0x08) ? 0x0f : 0x08); + setcolor(A_BOLD, bytes[2]); + + /* all others default to the "standout" colors, without blinking */ + for (i = 3; i < NCOLORS; i++) + { + bytes[i] = (bytes[1] & 0x7f); + setcolor(words[i].val, bytes[i]); + } + } + + /* force a redraw, so we see the new colors */ + redraw(MARK_UNSET, FALSE); +} + + + +void savecolor(fd) + int fd; /* file descriptor to write colors to */ +{ + int i; + char buf[80]; + + /* if no colors are set, then return */ + if (!bytes[0]) + { + return; + } + + /* save all five color combinations */ + for (i = 0; i < NCOLORS; i++) + { + strcpy(buf, "color "); + strcat(buf, words[i].word); + strcat(buf, " "); + if (bytes[i] & 0x80) + strcat(buf, "blinking "); + switch (bytes[i] & 0xf) + { + case 0x08: strcat(buf, "gray"); break; + case 0x0e: strcat(buf, "yellow"); break; + case 0x0f: strcat(buf, "bright white");break; + default: + if (bytes[i] & 0x08) + strcat(buf, "light "); + strcat(buf, words[(bytes[i] & 0x07) + NCOLORS].word); + } + strcat(buf, " on "); + strcat(buf, words[((bytes[i] >> 4) & 0x07) + NCOLORS].word); + strcat(buf, "\n"); + twrite(fd, buf, (unsigned)strlen(buf)); + } +} +#endif + +#ifdef SIGTSTP +/* temporarily suspend elvis */ +/*ARGSUSED*/ +void cmd_suspend(frommark, tomark, cmd, bang, extra) + MARK frommark; + MARK tomark; + CMD cmd; + int bang; + char *extra; +{ + void (*func)(); /* stores the previous setting of SIGTSTP */ + +#if !defined(__386BSD__) && defined(ANY_UNIX) + /* the Bourne shell can't handle ^Z */ + if (!strcmp(o_shell, "/bin/sh")) + { + msg("The /bin/sh shell doesn't support ^Z"); + return; + } +#endif + + func = signal(SIGTSTP, SIG_DFL); + if ( func == SIG_IGN ) { + msg("SIGTSTP is being ignored, you may not suspend the editor", func); + return; + } + move(LINES - 1, 0); + if (tstflag(file, MODIFIED)) + { + addstr("Warning: \""); + addstr(origname); + addstr("\" modified but not yet saved"); + clrtoeol(); + } + refresh(); + suspend_curses(); + /* was here func = signal(SIGTSTP, SIG_DFL); /* races ??? */ + kill (0, SIGTSTP); + + /* the process stops and resumes here */ + + signal(SIGTSTP, func); + resume_curses(TRUE); + if (mode == MODE_VI || mode == MODE_COLON) + redraw(MARK_UNSET, FALSE); + else + refresh (); +} +#endif -- 2.20.1