+/* 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 <stdio.h> */
+
+ /* 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