386BSD 0.1 development
authorWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Mon, 13 Jul 1992 10:02:27 +0000 (02:02 -0800)
committerWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Mon, 13 Jul 1992 10:02:27 +0000 (02:02 -0800)
Work on file usr/src/usr.bin/elvis/cmd1.c

Co-Authored-By: Lynne Greer Jolitz <ljolitz@cardio.ucsf.edu>
Synthesized-from: 386BSD-0.1

usr/src/usr.bin/elvis/cmd1.c [new file with mode: 0644]

diff --git a/usr/src/usr.bin/elvis/cmd1.c b/usr/src/usr.bin/elvis/cmd1.c
new file mode 100644 (file)
index 0000000..b322ec4
--- /dev/null
@@ -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 <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