]> pd.if.org Git - pd_readline/commitdiff
Added mg from an OpenBSD mirror site. Many thanks to the OpenBSD team and the mg...
authorandy <andy@obsidian.(none)>
Fri, 24 Aug 2012 11:26:29 +0000 (23:26 +1200)
committerandy <andy@obsidian.(none)>
Fri, 24 Aug 2012 11:26:29 +0000 (23:26 +1200)
51 files changed:
README
mg/Makefile [new file with mode: 0644]
mg/autoexec.c [new file with mode: 0644]
mg/basic.c [new file with mode: 0644]
mg/buffer.c [new file with mode: 0644]
mg/chrdef.h [new file with mode: 0644]
mg/cinfo.c [new file with mode: 0644]
mg/cmode.c [new file with mode: 0644]
mg/cscope.c [new file with mode: 0644]
mg/def.h [new file with mode: 0644]
mg/dir.c [new file with mode: 0644]
mg/dired.c [new file with mode: 0644]
mg/display.c [new file with mode: 0644]
mg/echo.c [new file with mode: 0644]
mg/extend.c [new file with mode: 0644]
mg/file.c [new file with mode: 0644]
mg/fileio.c [new file with mode: 0644]
mg/funmap.c [new file with mode: 0644]
mg/funmap.h [new file with mode: 0644]
mg/grep.c [new file with mode: 0644]
mg/help.c [new file with mode: 0644]
mg/kbd.c [new file with mode: 0644]
mg/kbd.h [new file with mode: 0644]
mg/key.h [new file with mode: 0644]
mg/keymap.c [new file with mode: 0644]
mg/line.c [new file with mode: 0644]
mg/macro.c [new file with mode: 0644]
mg/macro.h [new file with mode: 0644]
mg/main.c [new file with mode: 0644]
mg/match.c [new file with mode: 0644]
mg/mg.1 [new file with mode: 0644]
mg/modes.c [new file with mode: 0644]
mg/paragraph.c [new file with mode: 0644]
mg/pathnames.h [new file with mode: 0644]
mg/random.c [new file with mode: 0644]
mg/re_search.c [new file with mode: 0644]
mg/region.c [new file with mode: 0644]
mg/spawn.c [new file with mode: 0644]
mg/sysdef.h [new file with mode: 0644]
mg/tags.c [new file with mode: 0644]
mg/theo.c [new file with mode: 0644]
mg/tty.c [new file with mode: 0644]
mg/ttydef.h [new file with mode: 0644]
mg/ttyio.c [new file with mode: 0644]
mg/ttykbd.c [new file with mode: 0644]
mg/tutorial [new file with mode: 0644]
mg/undo.c [new file with mode: 0644]
mg/version.c [new file with mode: 0644]
mg/window.c [new file with mode: 0644]
mg/word.c [new file with mode: 0644]
mg/yank.c [new file with mode: 0644]

diff --git a/README b/README
index a69390f88d7e7f16f32fbd4b3a8b3992479214e4..b3a0eca4174a23f22af99ca1d06711d9413a23cb 100644 (file)
--- a/README
+++ b/README
@@ -4,6 +4,21 @@
   This repo is for the (eventual) storage of a public-domain  
 readline-and-command-history implementation. 
 
+Update - 24th Aug 2012 - 
+Added the mg editor files from OpenBSD. 
+I obtained the code from here - 
+ftp://ftp.cc.uoc.gr/mirrors/OpenBSD/src/usr.bin/mg/
+
+( It's not so much the editor itself that is the main interest here, 
+but the line-management code. )  
+
+NOTE - in the mg directory, the file "theo.c" is NOT 
+"public domain" ( but it HAD to be included because of the 
+humour......  ;)    )  
+
+Many thanks to the OpenBSD team and the mg devs 
+(hi Theo - arrrrrrgh, please don't hit me........... ;)     )   
+
  - mooseman 
 
  
diff --git a/mg/Makefile b/mg/Makefile
new file mode 100644 (file)
index 0000000..5b235e5
--- /dev/null
@@ -0,0 +1,34 @@
+# $OpenBSD: Makefile,v 1.27 2012/06/18 07:13:26 jasper Exp $
+
+PROG=  mg
+
+LDADD+=        -lcurses -lutil
+DPADD+=        ${LIBCURSES} ${LIBUTIL}
+
+# (Common) compile-time options:
+#
+#      FKEYS           -- add support for function key sequences.
+#      REGEX           -- create regular expression functions.
+#      STARTUP         -- look for and handle initialization file.
+#      XKEYS           -- use termcap function key definitions.
+#                              note: XKEYS and bsmap mode do _not_ get along.
+#
+CFLAGS+=-Wall -DFKEYS -DREGEX -DXKEYS
+
+SRCS=  autoexec.c basic.c buffer.c cinfo.c dir.c display.c \
+       echo.c extend.c file.c fileio.c funmap.c help.c kbd.c keymap.c \
+       line.c macro.c main.c match.c modes.c paragraph.c random.c \
+       re_search.c region.c search.c spawn.c tty.c ttyio.c ttykbd.c \
+       undo.c version.c window.c word.c yank.c
+
+#
+# More or less standalone extensions.
+#
+SRCS+= cmode.c cscope.c dired.c grep.c tags.c theo.c
+
+afterinstall:
+       ${INSTALL} -d ${DESTDIR}${DOCDIR}/mg
+       ${INSTALL} -m ${DOCMODE} -c ${.CURDIR}/tutorial \
+               ${DESTDIR}${DOCDIR}/mg
+
+
diff --git a/mg/autoexec.c b/mg/autoexec.c
new file mode 100644 (file)
index 0000000..ad3b08d
--- /dev/null
@@ -0,0 +1,111 @@
+/* $OpenBSD: autoexec.c,v 1.14 2007/02/08 21:40:03 kjell Exp $ */
+/* this file is in the public domain */
+/* Author: Vincent Labrecque <vincent@openbsd.org>     April 2002 */
+
+#include "def.h"
+#include "funmap.h"
+
+#include <fnmatch.h>
+
+struct autoexec {
+       SLIST_ENTRY(autoexec) next;     /* link in the linked list */
+       const char      *pattern;       /* Pattern to match to filenames */
+       PF               fp;
+};
+
+static SLIST_HEAD(, autoexec)   autos;
+static int                      ready;
+
+
+#define AUTO_GROW 8
+/*
+ * Return a NULL terminated array of function pointers to be called
+ * when we open a file that matches <fname>.  The list must be free(ed)
+ * after use.
+ */
+PF *
+find_autoexec(const char *fname)
+{
+       PF              *pfl, *npfl;
+       int              have, used;
+       struct autoexec *ae;
+
+       if (!ready)
+               return (NULL);
+
+       pfl = NULL;
+       have = 0;
+       used = 0;
+       SLIST_FOREACH(ae, &autos, next) {
+               if (fnmatch(ae->pattern, fname, 0) == 0) {
+                       if (used >= have) {
+                               npfl = realloc(pfl, (have + AUTO_GROW + 1) *
+                                   sizeof(PF));
+                               if (npfl == NULL)
+                                       panic("out of memory");
+                               pfl = npfl;
+                               have += AUTO_GROW;
+                       }
+                       pfl[used++] = ae->fp;
+               }
+       }
+       if (used)
+               pfl[used] = NULL;
+
+       return (pfl);
+}
+
+int
+add_autoexec(const char *pattern, const char *func)
+{
+       PF               fp;
+       struct autoexec *ae;
+
+       if (!ready) {
+               SLIST_INIT(&autos);
+               ready = 1;
+       }
+       fp = name_function(func);
+       if (fp == NULL)
+               return (FALSE);
+       ae = malloc(sizeof(*ae));
+       if (ae == NULL)
+               return (FALSE);
+       ae->fp = fp;
+       ae->pattern = strdup(pattern);
+       if (ae->pattern == NULL) {
+               free(ae);
+               return (FALSE);
+       }
+       SLIST_INSERT_HEAD(&autos, ae, next);
+
+       return (TRUE);
+}
+
+/*
+ * Register an auto-execute hook; that is, specify a filename pattern
+ * (conforming to the shell's filename globbing rules) and an associated
+ * function to execute when a file matching the specified pattern
+ * is read into a buffer. 
+*/
+/* ARGSUSED */
+int
+auto_execute(int f, int n)
+{
+       char    patbuf[128], funcbuf[128], *patp, *funcp;
+       int     s;
+
+       if ((patp = eread("Filename pattern: ", patbuf, sizeof(patbuf),
+           EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       else if (patp[0] == '\0')
+               return (FALSE);
+       if ((funcp = eread("Execute: ", funcbuf, sizeof(funcbuf),
+           EFNEW | EFCR | EFFUNC)) == NULL)
+               return (ABORT);
+       else if (funcp[0] == '\0')
+               return (FALSE);
+       if ((s = add_autoexec(patp, funcp)) != TRUE)
+               return (s);
+       return (TRUE);
+}
diff --git a/mg/basic.c b/mg/basic.c
new file mode 100644 (file)
index 0000000..160d9c1
--- /dev/null
@@ -0,0 +1,532 @@
+/*     $OpenBSD: basic.c,v 1.37 2012/06/18 09:26:03 lum Exp $  */
+
+/* This file is in the public domain */
+
+/*
+ *             Basic cursor motion commands.
+ *
+ * The routines in this file are the basic
+ * command functions for moving the cursor around on
+ * the screen, setting mark, and swapping dot with
+ * mark. Only moves between lines, which might make the
+ * current buffer framing bad, are hard.
+ */
+#include "def.h"
+
+#include <ctype.h>
+
+/*
+ * Go to beginning of line.
+ */
+/* ARGSUSED */
+int
+gotobol(int f, int n)
+{
+       curwp->w_doto = 0;
+       return (TRUE);
+}
+
+/*
+ * Move cursor backwards. Do the
+ * right thing if the count is less than
+ * 0. Error if you try to move back from
+ * the beginning of the buffer.
+ */
+/* ARGSUSED */
+int
+backchar(int f, int n)
+{
+       struct line   *lp;
+
+       if (n < 0)
+               return (forwchar(f, -n));
+       while (n--) {
+               if (curwp->w_doto == 0) {
+                       if ((lp = lback(curwp->w_dotp)) == curbp->b_headp) {
+                               if (!(f & FFRAND))
+                                       ewprintf("Beginning of buffer");
+                               return (FALSE);
+                       }
+                       curwp->w_dotp = lp;
+                       curwp->w_doto = llength(lp);
+                       curwp->w_rflag |= WFMOVE;
+                       curwp->w_dotline--;
+               } else
+                       curwp->w_doto--;
+       }
+       return (TRUE);
+}
+
+/*
+ * Go to end of line.
+ */
+/* ARGSUSED */
+int
+gotoeol(int f, int n)
+{
+       curwp->w_doto = llength(curwp->w_dotp);
+       return (TRUE);
+}
+
+/*
+ * Move cursor forwards. Do the
+ * right thing if the count is less than
+ * 0. Error if you try to move forward
+ * from the end of the buffer.
+ */
+/* ARGSUSED */
+int
+forwchar(int f, int n)
+{
+       if (n < 0)
+               return (backchar(f, -n));
+       while (n--) {
+               if (curwp->w_doto == llength(curwp->w_dotp)) {
+                       curwp->w_dotp = lforw(curwp->w_dotp);
+                       if (curwp->w_dotp == curbp->b_headp) {
+                               curwp->w_dotp = lback(curwp->w_dotp);
+                               if (!(f & FFRAND))
+                                       ewprintf("End of buffer");
+                               return (FALSE);
+                       }
+                       curwp->w_doto = 0;
+                       curwp->w_dotline++;
+                       curwp->w_rflag |= WFMOVE;
+               } else
+                       curwp->w_doto++;
+       }
+       return (TRUE);
+}
+
+/*
+ * Go to the beginning of the
+ * buffer. Setting WFFULL is conservative,
+ * but almost always the case.
+ */
+int
+gotobob(int f, int n)
+{
+       (void) setmark(f, n);
+       curwp->w_dotp = bfirstlp(curbp);
+       curwp->w_doto = 0;
+       curwp->w_rflag |= WFFULL;
+       curwp->w_dotline = 1;
+       return (TRUE);
+}
+
+/*
+ * Go to the end of the buffer. Leave dot 3 lines from the bottom of the
+ * window if buffer length is longer than window length; same as emacs.
+ * Setting WFFULL is conservative, but almost always the case.
+ */
+int
+gotoeob(int f, int n)
+{
+       struct line     *lp;
+       
+       (void) setmark(f, n);
+       curwp->w_dotp = blastlp(curbp);
+       curwp->w_doto = llength(curwp->w_dotp);
+       curwp->w_dotline = curwp->w_bufp->b_lines;
+
+       lp = curwp->w_dotp;
+       n = curwp->w_ntrows - 3;
+
+       if (n < curwp->w_bufp->b_lines && n >= 3) {
+               while (n--)
+                       curwp->w_dotp = lback(curwp->w_dotp);
+
+               curwp->w_linep = curwp->w_dotp;
+               curwp->w_dotp = lp;
+       }
+       curwp->w_rflag |= WFFULL;
+       return (TRUE);
+}
+
+/*
+ * Move forward by full lines.
+ * If the number of lines to move is less
+ * than zero, call the backward line function to
+ * actually do it. The last command controls how
+ * the goal column is set.
+ */
+/* ARGSUSED */
+int
+forwline(int f, int n)
+{
+       struct line  *dlp;
+
+       if (n < 0)
+               return (backline(f | FFRAND, -n));
+       if ((dlp = curwp->w_dotp) == curbp->b_headp)
+               return(TRUE);
+       if ((lastflag & CFCPCN) == 0)   /* Fix goal. */
+               setgoal();
+       thisflag |= CFCPCN;
+       if (n == 0)
+               return (TRUE);
+       while (n--) {
+               dlp = lforw(dlp);
+               if (dlp == curbp->b_headp) {
+                       curwp->w_dotp = lback(dlp);
+                       curwp->w_doto = llength(curwp->w_dotp);
+                       curwp->w_rflag |= WFMOVE;
+                       return (TRUE);
+               }
+               curwp->w_dotline++;
+       }
+       curwp->w_rflag |= WFMOVE;
+       curwp->w_dotp = dlp;
+       curwp->w_doto = getgoal(dlp);
+
+       return (TRUE);
+}
+
+/*
+ * This function is like "forwline", but
+ * goes backwards. The scheme is exactly the same.
+ * Check for arguments that are less than zero and
+ * call your alternate. Figure out the new line and
+ * call "movedot" to perform the motion.
+ */
+/* ARGSUSED */
+int
+backline(int f, int n)
+{
+       struct line   *dlp;
+
+       if (n < 0)
+               return (forwline(f | FFRAND, -n));
+       if ((lastflag & CFCPCN) == 0)   /* Fix goal. */
+               setgoal();
+       thisflag |= CFCPCN;
+       dlp = curwp->w_dotp;
+       while (n-- && lback(dlp) != curbp->b_headp) {
+               dlp = lback(dlp);
+               curwp->w_dotline--;
+       }
+       curwp->w_dotp = dlp;
+       curwp->w_doto = getgoal(dlp);
+       curwp->w_rflag |= WFMOVE;
+       return (TRUE);
+}
+
+/*
+ * Set the current goal column, which is saved in the external variable
+ * "curgoal", to the current cursor column. The column is never off
+ * the edge of the screen; it's more like display then show position.
+ */
+void
+setgoal(void)
+{
+       curgoal = getcolpos();          /* Get the position. */
+       /* we can now display past end of display, don't chop! */
+}
+
+/*
+ * This routine looks at a line (pointed
+ * to by the LINE pointer "dlp") and the current
+ * vertical motion goal column (set by the "setgoal"
+ * routine above) and returns the best offset to use
+ * when a vertical motion is made into the line.
+ */
+int
+getgoal(struct line *dlp)
+{
+       int c, i, col = 0;
+       char tmp[5];
+
+
+       for (i = 0; i < llength(dlp); i++) {
+               c = lgetc(dlp, i);
+               if (c == '\t'
+#ifdef NOTAB
+                   && !(curbp->b_flag & BFNOTAB)
+#endif
+                       ) {
+                       col |= 0x07;
+                       col++;
+               } else if (ISCTRL(c) != FALSE) {
+                       col += 2;
+               } else if (isprint(c))
+                       col++;
+               else {
+                       col += snprintf(tmp, sizeof(tmp), "\\%o", c);
+               }
+               if (col > curgoal)
+                       break;
+       }
+       return (i);
+}
+
+/*
+ * Scroll forward by a specified number
+ * of lines, or by a full page if no argument.
+ * The "2" is the window overlap (this is the default
+ * value from ITS EMACS). Because the top line in
+ * the window is zapped, we have to do a hard
+ * update and get it back.
+ */
+/* ARGSUSED */
+int
+forwpage(int f, int n)
+{
+       struct line  *lp;
+
+       if (!(f & FFARG)) {
+               n = curwp->w_ntrows - 2;        /* Default scroll.       */
+               if (n <= 0)                     /* Forget the overlap    */
+                       n = 1;                  /* if tiny window.       */
+       } else if (n < 0)
+               return (backpage(f | FFRAND, -n));
+
+       lp = curwp->w_linep;
+       while (n--)
+               if ((lp = lforw(lp)) == curbp->b_headp) {
+                       ttbeep();
+                       ewprintf("End of buffer");
+                       return(TRUE);
+               }
+
+       curwp->w_linep = lp;
+       curwp->w_rflag |= WFFULL;
+
+       /* if in current window, don't move dot */
+       for (n = curwp->w_ntrows; n-- && lp != curbp->b_headp; lp = lforw(lp))
+               if (lp == curwp->w_dotp)
+                       return (TRUE);
+
+       /* Advance the dot the slow way, for line nos */
+       while (curwp->w_dotp != curwp->w_linep) {
+               curwp->w_dotp = lforw(curwp->w_dotp);
+               curwp->w_dotline++;
+       }
+       curwp->w_doto = 0;
+       return (TRUE);
+}
+
+/*
+ * This command is like "forwpage",
+ * but it goes backwards. The "2", like above,
+ * is the overlap between the two windows. The
+ * value is from the ITS EMACS manual. The
+ * hard update is done because the top line in
+ * the window is zapped.
+ */
+/* ARGSUSED */
+int
+backpage(int f, int n)
+{
+       struct line  *lp, *lp2;
+
+       if (!(f & FFARG)) {
+               n = curwp->w_ntrows - 2;        /* Default scroll.       */
+               if (n <= 0)                     /* Don't blow up if the  */
+                       return (backline(f, 1));/* window is tiny.       */
+       } else if (n < 0)
+               return (forwpage(f | FFRAND, -n));
+
+       lp = lp2 = curwp->w_linep;
+
+       while (n-- && lback(lp) != curbp->b_headp) {
+               lp = lback(lp);
+       }
+       if (lp == curwp->w_linep) {
+               ttbeep();
+               ewprintf("Beginning of buffer");
+       }
+       curwp->w_linep = lp;
+       curwp->w_rflag |= WFFULL;
+
+       /* if in current window, don't move dot */
+       for (n = curwp->w_ntrows; n-- && lp != curbp->b_headp; lp = lforw(lp))
+               if (lp == curwp->w_dotp)
+                       return (TRUE);
+
+        lp2 = lforw(lp2);
+
+       /* Move the dot the slow way, for line nos */
+       while (curwp->w_dotp != lp2) {
+                if (curwp->w_dotline <= curwp->w_ntrows)
+                        return (TRUE);
+               curwp->w_dotp = lback(curwp->w_dotp);
+               curwp->w_dotline--;
+       }
+       curwp->w_doto = 0;
+       return (TRUE);
+}
+
+/*
+ * These functions are provided for compatibility with Gosling's Emacs. They
+ * are used to scroll the display up (or down) one line at a time.
+ */
+int
+forw1page(int f, int n)
+{
+       if (!(f & FFARG)) {
+               n = 1;
+               f = FFUNIV;
+       }
+       forwpage(f | FFRAND, n);
+       return (TRUE);
+}
+
+int
+back1page(int f, int n)
+{
+       if (!(f & FFARG)) {
+               n = 1;
+               f = FFUNIV;
+       }
+       backpage(f | FFRAND, n);
+       return (TRUE);
+}
+
+/*
+ * Page the other window. Check to make sure it exists, then
+ * nextwind, forwpage and restore window pointers.
+ */
+int
+pagenext(int f, int n)
+{
+       struct mgwin *wp;
+
+       if (wheadp->w_wndp == NULL) {
+               ewprintf("No other window");
+               return (FALSE);
+       }
+       wp = curwp;
+       (void) nextwind(f, n);
+       (void) forwpage(f, n);
+       curwp = wp;
+       curbp = wp->w_bufp;
+       return (TRUE);
+}
+
+/*
+ * Internal set mark routine, used by other functions (daveb).
+ */
+void
+isetmark(void)
+{
+       curwp->w_markp = curwp->w_dotp;
+       curwp->w_marko = curwp->w_doto;
+       curwp->w_markline = curwp->w_dotline;
+}
+
+/*
+ * Set the mark in the current window
+ * to the value of dot. A message is written to
+ * the echo line.  (ewprintf knows about macros)
+ */
+/* ARGSUSED */
+int
+setmark(int f, int n)
+{
+       isetmark();
+       ewprintf("Mark set");
+       return (TRUE);
+}
+
+/* Clear the mark, if set. */
+/* ARGSUSED */
+int
+clearmark(int f, int n)
+{
+       if (!curwp->w_markp)
+               return (FALSE);
+
+       curwp->w_markp = NULL;
+       curwp->w_marko = 0;
+       curwp->w_markline = 0;
+
+       return (TRUE);
+}
+
+/*
+ * Swap the values of "dot" and "mark" in
+ * the current window. This is pretty easy, because
+ * all of the hard work gets done by the standard routine
+ * that moves the mark about. The only possible
+ * error is "no mark".
+ */
+/* ARGSUSED */
+int
+swapmark(int f, int n)
+{
+       struct line  *odotp;
+       int odoto, odotline;
+
+       if (curwp->w_markp == NULL) {
+               ewprintf("No mark in this window");
+               return (FALSE);
+       }
+       odotp = curwp->w_dotp;
+       odoto = curwp->w_doto;
+       odotline = curwp->w_dotline;
+       curwp->w_dotp = curwp->w_markp;
+       curwp->w_doto = curwp->w_marko;
+       curwp->w_dotline = curwp->w_markline;
+       curwp->w_markp = odotp;
+       curwp->w_marko = odoto;
+       curwp->w_markline = odotline;
+       curwp->w_rflag |= WFMOVE;
+       return (TRUE);
+}
+
+/*
+ * Go to a specific line, mostly for
+ * looking up errors in C programs, which give the
+ * error a line number. If an argument is present, then
+ * it is the line number, else prompt for a line number
+ * to use.
+ */
+/* ARGSUSED */
+int
+gotoline(int f, int n)
+{
+       struct line  *clp;
+       char   buf[32], *bufp;
+       const char *err;
+
+       if (!(f & FFARG)) {
+               if ((bufp = eread("Goto line: ", buf, sizeof(buf),
+                   EFNUL | EFNEW | EFCR)) == NULL)
+                       return (ABORT);
+               if (bufp[0] == '\0')
+                       return (ABORT);
+               n = (int)strtonum(buf, INT_MIN, INT_MAX, &err);
+               if (err) {
+                       ewprintf("Line number %s", err);
+                       return (FALSE);
+               }
+       }
+       if (n >= 0) {
+               if (n == 0)
+                       n++;
+               curwp->w_dotline = n;
+               clp = lforw(curbp->b_headp);    /* "clp" is first line */
+               while (--n > 0) {
+                       if (lforw(clp) == curbp->b_headp) {
+                               curwp->w_dotline = curwp->w_bufp->b_lines;
+                               break;
+                       }
+                       clp = lforw(clp);
+               }
+       } else {
+               curwp->w_dotline = curwp->w_bufp->b_lines + n;
+               clp = lback(curbp->b_headp);    /* "clp" is last line */
+               while (n < 0) {
+                       if (lback(clp) == curbp->b_headp) {
+                               curwp->w_dotline = 1;
+                               break;
+                       }
+                       clp = lback(clp);
+                       n++;
+               }
+       }
+       curwp->w_dotp = clp;
+       curwp->w_doto = 0;
+       curwp->w_rflag |= WFMOVE;
+       return (TRUE);
+}
diff --git a/mg/buffer.c b/mg/buffer.c
new file mode 100644 (file)
index 0000000..0a4254c
--- /dev/null
@@ -0,0 +1,863 @@
+/*     $OpenBSD: buffer.c,v 1.78 2012/03/14 13:56:35 lum Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ *             Buffer handling.
+ */
+
+#include "def.h"
+#include "kbd.h"               /* needed for modes */
+
+#include <libgen.h>
+#include <stdarg.h>
+
+static struct buffer  *makelist(void);
+static struct buffer *bnew(const char *);
+
+static int usebufname(const char *);
+
+/* Flag for global working dir */
+extern int globalwd;
+
+/* ARGSUSED */
+int
+togglereadonly(int f, int n)
+{
+       int s;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (!(curbp->b_flag & BFREADONLY))
+               curbp->b_flag |= BFREADONLY;
+       else {
+               curbp->b_flag &= ~BFREADONLY;
+               if (curbp->b_flag & BFCHG)
+                       ewprintf("Warning: Buffer was modified");
+       }
+       curwp->w_rflag |= WFMODE;
+
+       return (TRUE);
+}
+
+/* Switch to the named buffer.
+ * If no name supplied, switch to the default (alternate) buffer.
+ */
+int
+usebufname(const char *bufp)
+{
+       struct buffer *bp;
+
+       if (bufp == NULL)
+               return (ABORT);
+       if (bufp[0] == '\0' && curbp->b_altb != NULL)
+               bp = curbp->b_altb;
+       else if ((bp = bfind(bufp, TRUE)) == NULL)
+               return (FALSE);
+
+       /* and put it in current window */
+       curbp = bp;
+       return (showbuffer(bp, curwp, WFFRAME | WFFULL));
+}
+
+/*
+ * Attach a buffer to a window. The values of dot and mark come
+ * from the buffer if the use count is 0. Otherwise, they come
+ * from some other window.  *scratch* is the default alternate
+ * buffer.
+ */
+/* ARGSUSED */
+int
+usebuffer(int f, int n)
+{
+       char    bufn[NBUFN], *bufp;
+
+       /* Get buffer to use from user */
+       if ((curbp->b_altb == NULL) &&
+           ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL))
+               bufp = eread("Switch to buffer: ", bufn, NBUFN, EFNEW | EFBUF);
+       else
+               bufp = eread("Switch to buffer: (default %s) ", bufn, NBUFN,
+                   EFNUL | EFNEW | EFBUF, curbp->b_altb->b_bname);
+
+       return (usebufname(bufp));
+}
+
+/*
+ * pop to buffer asked for by the user.
+ */
+/* ARGSUSED */
+int
+poptobuffer(int f, int n)
+{
+       struct buffer *bp;
+       struct mgwin  *wp;
+       char    bufn[NBUFN], *bufp;
+
+       /* Get buffer to use from user */
+       if ((curbp->b_altb == NULL) &&
+           ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL))
+               bufp = eread("Switch to buffer in other window: ", bufn, NBUFN,
+                   EFNEW | EFBUF);
+       else
+               bufp = eread("Switch to buffer in other window: (default %s) ",
+                   bufn, NBUFN, EFNUL | EFNEW | EFBUF, curbp->b_altb->b_bname);
+       if (bufp == NULL)
+               return (ABORT);
+       if (bufp[0] == '\0' && curbp->b_altb != NULL)
+               bp = curbp->b_altb;
+       else if ((bp = bfind(bufn, TRUE)) == NULL)
+               return (FALSE);
+       if (bp == curbp)
+               return (splitwind(f, n));
+       /* and put it in a new, non-ephemeral window */
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       curwp = wp;
+       return (TRUE);
+}
+
+/*
+ * Dispose of a buffer, by name.
+ * Ask for the name. Look it up (don't get too
+ * upset if it isn't there at all!). Clear the buffer (ask
+ * if the buffer has been changed). Then free the header
+ * line and the buffer header. Bound to "C-X k".
+ */
+/* ARGSUSED */
+int
+killbuffer_cmd(int f, int n)
+{
+       struct buffer *bp;
+       char    bufn[NBUFN], *bufp;
+
+       if ((bufp = eread("Kill buffer: (default %s) ", bufn, NBUFN,
+           EFNUL | EFNEW | EFBUF, curbp->b_bname)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               bp = curbp;
+       else if ((bp = bfind(bufn, FALSE)) == NULL)
+               return (FALSE);
+       return (killbuffer(bp));
+}
+
+int
+killbuffer(struct buffer *bp)
+{
+       struct buffer *bp1;
+       struct buffer *bp2;
+       struct mgwin  *wp;
+       int s;
+       struct undo_rec *rec, *next;
+
+       /*
+        * Find some other buffer to display. Try the alternate buffer,
+        * then the first different buffer in the buffer list.  If there's
+        * only one buffer, create buffer *scratch* and make it the alternate
+        * buffer.  Return if *scratch* is only buffer...
+        */
+       if ((bp1 = bp->b_altb) == NULL) {
+               bp1 = (bp == bheadp) ? bp->b_bufp : bheadp;
+               if (bp1 == NULL) {
+                       /* only one buffer. see if it's *scratch* */
+                       if (bp == bfind("*scratch*", FALSE))
+                               return (TRUE);
+                       /* create *scratch* for alternate buffer */
+                       if ((bp1 = bfind("*scratch*", TRUE)) == NULL)
+                               return (FALSE);
+               }
+       }
+       if ((s = bclear(bp)) != TRUE)
+               return (s);
+       for (wp = wheadp; bp->b_nwnd > 0; wp = wp->w_wndp) {
+               if (wp->w_bufp == bp) {
+                       bp2 = bp1->b_altb;      /* save alternate buffer */
+                       if (showbuffer(bp1, wp, WFMODE | WFFRAME | WFFULL))
+                               bp1->b_altb = bp2;
+                       else
+                               bp1 = bp2;
+               }
+       }
+       if (bp == curbp)
+               curbp = bp1;
+       free(bp->b_headp);                      /* Release header line.  */
+       bp2 = NULL;                             /* Find the header.      */
+       bp1 = bheadp;
+       while (bp1 != bp) {
+               if (bp1->b_altb == bp)
+                       bp1->b_altb = (bp->b_altb == bp1) ? NULL : bp->b_altb;
+               bp2 = bp1;
+               bp1 = bp1->b_bufp;
+       }
+       bp1 = bp1->b_bufp;                      /* Next one in chain.    */
+       if (bp2 == NULL)                        /* Unlink it.            */
+               bheadp = bp1;
+       else
+               bp2->b_bufp = bp1;
+       while (bp1 != NULL) {                   /* Finish with altb's    */
+               if (bp1->b_altb == bp)
+                       bp1->b_altb = (bp->b_altb == bp1) ? NULL : bp->b_altb;
+               bp1 = bp1->b_bufp;
+       }
+       rec = TAILQ_FIRST(&bp->b_undo);
+
+       while (rec != NULL) {
+               next = TAILQ_NEXT(rec, next);
+               free_undo_record(rec);
+               rec = next;
+       }
+
+       free(bp->b_bname);                      /* Release name block    */
+       free(bp);                               /* Release buffer block */
+       return (TRUE);
+}
+
+/*
+ * Save some buffers - just call anycb with the arg flag.
+ */
+/* ARGSUSED */
+int
+savebuffers(int f, int n)
+{
+       if (anycb(f) == ABORT)
+               return (ABORT);
+       return (TRUE);
+}
+
+/*
+ * Listing buffers.
+ */
+static int listbuf_ncol;
+
+static int     listbuf_goto_buffer(int f, int n);
+static int     listbuf_goto_buffer_one(int f, int n);
+static int     listbuf_goto_buffer_helper(int f, int n, int only);
+
+static PF listbuf_pf[] = {
+       listbuf_goto_buffer
+};
+static PF listbuf_one[] = {
+       listbuf_goto_buffer_one
+};
+
+
+static struct KEYMAPE (2 + IMAPEXT) listbufmap = {
+       2,
+       2 + IMAPEXT,
+       rescan,
+       {
+               {
+                       CCHR('M'), CCHR('M'), listbuf_pf, NULL
+               },
+               {
+                       '1', '1', listbuf_one, NULL
+               }
+       }
+};
+
+/*
+ * Display the buffer list. This is done
+ * in two parts. The "makelist" routine figures out
+ * the text, and puts it in a buffer. "popbuf"
+ * then pops the data onto the screen. Bound to
+ * "C-X C-B".
+ */
+/* ARGSUSED */
+int
+listbuffers(int f, int n)
+{
+       static int               initialized = 0;
+       struct buffer           *bp;
+       struct mgwin            *wp;
+
+       if (!initialized) {
+               maps_add((KEYMAP *)&listbufmap, "listbufmap");
+               initialized = 1;
+       }
+
+       if ((bp = makelist()) == NULL || (wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+       wp->w_dotp = bp->b_dotp; /* fix up if window already on screen */
+       wp->w_doto = bp->b_doto;
+       bp->b_modes[0] = name_mode("fundamental");
+       bp->b_modes[1] = name_mode("listbufmap");
+       bp->b_nmodes = 1;
+
+       return (TRUE);
+}
+
+/*
+ * This routine rebuilds the text for the
+ * list buffers command. Return pointer
+ * to new list if everything works.
+ * Return NULL if there is an error (if
+ * there is no memory).
+ */
+static struct buffer *
+makelist(void)
+{
+       int             w = ncol / 2;
+       struct buffer   *bp, *blp;
+       struct line     *lp;
+
+       if ((blp = bfind("*Buffer List*", TRUE)) == NULL)
+               return (NULL);
+       if (bclear(blp) != TRUE)
+               return (NULL);
+       blp->b_flag &= ~BFCHG;          /* Blow away old.        */
+       blp->b_flag |= BFREADONLY;
+
+       listbuf_ncol = ncol;            /* cache ncol for listbuf_goto_buffer */
+
+       if (addlinef(blp, "%-*s%s", w, " MR Buffer", "Size   File") == FALSE ||
+           addlinef(blp, "%-*s%s", w, " -- ------", "----   ----") == FALSE)
+               return (NULL);
+
+       for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
+               RSIZE nbytes;
+
+               nbytes = 0;                     /* Count bytes in buf.   */
+               if (bp != blp) {
+                       lp = bfirstlp(bp);
+                       while (lp != bp->b_headp) {
+                               nbytes += llength(lp) + 1;
+                               lp = lforw(lp);
+                       }
+                       if (nbytes)
+                               nbytes--;       /* no bonus newline      */
+               }
+
+               if (addlinef(blp, "%c%c%c %-*.*s%c%-6d %-*s",
+                   (bp == curbp) ? '.' : ' ',  /* current buffer ? */
+                   ((bp->b_flag & BFCHG) != 0) ? '*' : ' ',    /* changed ? */
+                   ((bp->b_flag & BFREADONLY) != 0) ? ' ' : '*',
+                   w - 5,              /* four chars already written */
+                   w - 5,              /* four chars already written */
+                   bp->b_bname,        /* buffer name */
+                   strlen(bp->b_bname) < w - 5 ? ' ' : '$', /* truncated? */
+                   nbytes,             /* buffer size */
+                   w - 7,              /* seven chars already written */
+                   bp->b_fname) == FALSE)
+                       return (NULL);
+       }
+       blp->b_dotp = bfirstlp(blp);            /* put dot at beginning of
+                                                * buffer */
+       blp->b_doto = 0;
+       return (blp);                           /* All done              */
+}
+
+static int
+listbuf_goto_buffer(int f, int n)
+{
+       return (listbuf_goto_buffer_helper(f, n, 0));
+}
+
+static int
+listbuf_goto_buffer_one(int f, int n)
+{
+       return (listbuf_goto_buffer_helper(f, n, 1));
+}
+
+static int
+listbuf_goto_buffer_helper(int f, int n, int only)
+{
+       struct buffer   *bp;
+       struct mgwin    *wp;
+       char            *line = NULL;
+       int              i, ret = FALSE;
+
+       if (curwp->w_dotp->l_text[listbuf_ncol/2 - 1] == '$') {
+               ewprintf("buffer name truncated");
+               return (FALSE);
+       }
+
+       if ((line = malloc(listbuf_ncol/2)) == NULL)
+               return (FALSE);
+
+       memcpy(line, curwp->w_dotp->l_text + 4, listbuf_ncol/2 - 5);
+       for (i = listbuf_ncol/2 - 6; i > 0; i--) {
+               if (line[i] != ' ') {
+                       line[i + 1] = '\0';
+                       break;
+               }
+       }
+       if (i == 0)
+               goto cleanup;
+
+       for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
+               if (strcmp(bp->b_bname, line) == 0)
+                       break;
+       }
+       if (bp == NULL)
+               goto cleanup;
+
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               goto cleanup;
+       curbp = bp;
+       curwp = wp;
+
+       if (only)
+               ret = (onlywind(f, n));
+       else
+               ret = TRUE;
+
+cleanup:
+       free(line);
+
+       return (ret);
+}
+
+/*
+ * The argument "fmt" points to a format string.  Append this line to the
+ * buffer. Handcraft the EOL on the end.  Return TRUE if it worked and
+ * FALSE if you ran out of room.
+ */
+int
+addlinef(struct buffer *bp, char *fmt, ...)
+{
+       va_list          ap;
+       struct line     *lp;
+
+       if ((lp = lalloc(0)) == NULL)
+               return (FALSE);
+       va_start(ap, fmt);
+       if (vasprintf(&lp->l_text, fmt, ap) == -1) {
+               lfree(lp);
+               va_end(ap);
+               return (FALSE);
+       }
+       lp->l_used = strlen(lp->l_text);
+       va_end(ap);
+
+       bp->b_headp->l_bp->l_fp = lp;           /* Hook onto the end     */
+       lp->l_bp = bp->b_headp->l_bp;
+       bp->b_headp->l_bp = lp;
+       lp->l_fp = bp->b_headp;
+       bp->b_lines++;
+
+       return (TRUE);
+}
+
+/*
+ * Look through the list of buffers, giving the user a chance to save them.
+ * Return TRUE if there are any changed buffers afterwards.  Buffers that don't
+ * have an associated file don't count.  Return FALSE if there are no changed
+ * buffers.  Return ABORT if an error occurs or if the user presses c-g.
+ */
+int
+anycb(int f)
+{
+       struct buffer   *bp;
+       int              s = FALSE, save = FALSE, ret;
+       char             pbuf[NFILEN + 11];
+
+       for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
+               if (bp->b_fname != NULL && *(bp->b_fname) != '\0' &&
+                   (bp->b_flag & BFCHG) != 0) {
+                       ret = snprintf(pbuf, sizeof(pbuf), "Save file %s",
+                           bp->b_fname);
+                       if (ret < 0 || ret >= sizeof(pbuf)) {
+                               ewprintf("Error: filename too long!");
+                               return (ABORT);
+                       }
+                       if ((f == TRUE || (save = eyorn(pbuf)) == TRUE) &&
+                           buffsave(bp) == TRUE) {
+                               bp->b_flag &= ~BFCHG;
+                               upmodes(bp);
+                       } else
+                               s = TRUE;
+                       if (save == ABORT)
+                               return (save);
+                       save = TRUE;
+               }
+       }
+       if (save == FALSE /* && kbdmop == NULL */ )     /* experimental */
+               ewprintf("(No files need saving)");
+       return (s);
+}
+
+/*
+ * Search for a buffer, by name.
+ * If not found, and the "cflag" is TRUE,
+ * create a new buffer. Return pointer to the found
+ * (or new) buffer.
+ */
+struct buffer *
+bfind(const char *bname, int cflag)
+{
+       struct buffer   *bp;
+
+       bp = bheadp;
+       while (bp != NULL) {
+               if (strcmp(bname, bp->b_bname) == 0)
+                       return (bp);
+               bp = bp->b_bufp;
+       }
+       if (cflag != TRUE)
+               return (NULL);
+
+       bp = bnew(bname);
+
+       return (bp);
+}
+
+/*
+ * Create a new buffer and put it in the list of
+ * all buffers.
+ */
+static struct buffer *
+bnew(const char *bname)
+{
+       struct buffer   *bp;
+       struct line     *lp;
+       int              i;
+       size_t          len;
+
+       bp = calloc(1, sizeof(struct buffer));
+       if (bp == NULL) {
+               ewprintf("Can't get %d bytes", sizeof(struct buffer));
+               return (NULL);
+       }
+       if ((lp = lalloc(0)) == NULL) {
+               free(bp);
+               return (NULL);
+       }
+       bp->b_altb = bp->b_bufp = NULL;
+       bp->b_dotp = lp;
+       bp->b_doto = 0;
+       bp->b_markp = NULL;
+       bp->b_marko = 0;
+       bp->b_flag = defb_flag;
+       /* if buffer name starts and ends with '*', we ignore changes */
+       len = strlen(bname);
+       if (len) {
+               if (bname[0] == '*' && bname[len - 1] == '*')
+                       bp->b_flag |= BFIGNDIRTY;
+       }
+       bp->b_nwnd = 0;
+       bp->b_headp = lp;
+       bp->b_nmodes = defb_nmodes;
+       TAILQ_INIT(&bp->b_undo);
+       bp->b_undoptr = NULL;
+       memset(&bp->b_undopos, 0, sizeof(bp->b_undopos));
+       i = 0;
+       do {
+               bp->b_modes[i] = defb_modes[i];
+       } while (i++ < defb_nmodes);
+       bp->b_fname[0] = '\0';
+       bp->b_cwd[0] = '\0';
+       bzero(&bp->b_fi, sizeof(bp->b_fi));
+       lp->l_fp = lp;
+       lp->l_bp = lp;
+       bp->b_bufp = bheadp;
+       bheadp = bp;
+       bp->b_dotline = bp->b_markline = 1;
+       bp->b_lines = 1;
+       if ((bp->b_bname = strdup(bname)) == NULL) {
+               ewprintf("Can't get %d bytes", strlen(bname) + 1);
+               return (NULL);
+       }
+
+       return (bp);
+}
+
+/*
+ * This routine blows away all of the text
+ * in a buffer. If the buffer is marked as changed
+ * then we ask if it is ok to blow it away; this is
+ * to save the user the grief of losing text. The
+ * window chain is nearly always wrong if this gets
+ * called; the caller must arrange for the updates
+ * that are required. Return TRUE if everything
+ * looks good.
+ */
+int
+bclear(struct buffer *bp)
+{
+       struct line     *lp;
+       int              s;
+
+       /* Has buffer changed, and do we care? */
+       if (!(bp->b_flag & BFIGNDIRTY) && (bp->b_flag & BFCHG) != 0 &&
+           (s = eyesno("Buffer modified; kill anyway")) != TRUE)
+               return (s);
+       bp->b_flag &= ~BFCHG;   /* Not changed           */
+       while ((lp = lforw(bp->b_headp)) != bp->b_headp)
+               lfree(lp);
+       bp->b_dotp = bp->b_headp;       /* Fix dot */
+       bp->b_doto = 0;
+       bp->b_markp = NULL;     /* Invalidate "mark"     */
+       bp->b_marko = 0;
+       bp->b_dotline = bp->b_markline = 1;
+       bp->b_lines = 1;
+
+       return (TRUE);
+}
+
+/*
+ * Display the given buffer in the given window. Flags indicated
+ * action on redisplay. Update modified flag so insert loop can check it.
+ */
+int
+showbuffer(struct buffer *bp, struct mgwin *wp, int flags)
+{
+       struct buffer   *obp;
+       struct mgwin    *owp;
+
+       /* Ensure file has not been modified elsewhere */
+       if (fchecktime(bp) != TRUE)
+               bp->b_flag |= BFDIRTY;
+
+       if (wp->w_bufp == bp) { /* Easy case! */
+               wp->w_rflag |= flags;
+               wp->w_dotp = bp->b_dotp;
+               wp->w_doto = bp->b_doto;
+               return (TRUE);
+       }
+       /* First, detach the old buffer from the window */
+       if ((bp->b_altb = obp = wp->w_bufp) != NULL) {
+               if (--obp->b_nwnd == 0) {
+                       obp->b_dotp = wp->w_dotp;
+                       obp->b_doto = wp->w_doto;
+                       obp->b_markp = wp->w_markp;
+                       obp->b_marko = wp->w_marko;
+                       obp->b_dotline = wp->w_dotline;
+                       obp->b_markline = wp->w_markline;
+               }
+       }
+       /* Now, attach the new buffer to the window */
+       wp->w_bufp = bp;
+
+       if (bp->b_nwnd++ == 0) {        /* First use.            */
+               wp->w_dotp = bp->b_dotp;
+               wp->w_doto = bp->b_doto;
+               wp->w_markp = bp->b_markp;
+               wp->w_marko = bp->b_marko;
+               wp->w_dotline = bp->b_dotline;
+               wp->w_markline = bp->b_markline;
+       } else
+               /* already on screen, steal values from other window */
+               for (owp = wheadp; owp != NULL; owp = wp->w_wndp)
+                       if (wp->w_bufp == bp && owp != wp) {
+                               wp->w_dotp = owp->w_dotp;
+                               wp->w_doto = owp->w_doto;
+                               wp->w_markp = owp->w_markp;
+                               wp->w_marko = owp->w_marko;
+                               wp->w_dotline = owp->w_dotline;
+                               wp->w_markline = owp->w_markline;
+                               break;
+                       }
+       wp->w_rflag |= WFMODE | flags;
+       return (TRUE);
+}
+
+/*
+ * Augment a buffer name with a number, if necessary
+ *
+ * If more than one file of the same basename() is open,
+ * the additional buffers are named "file<2>", "file<3>", and
+ * so forth.  This function adjusts a buffer name to
+ * include the number, if necessary.
+ */
+int
+augbname(char *bn, const char *fn, size_t bs)
+{
+       int      count;
+       size_t   remain, len;
+
+       if ((len = xbasename(bn, fn, bs)) >= bs)
+               return (FALSE);
+
+       remain = bs - len;
+       for (count = 2; bfind(bn, FALSE) != NULL; count++)
+               snprintf(bn + len, remain, "<%d>", count);
+
+       return (TRUE);
+}
+
+/*
+ * Pop the buffer we got passed onto the screen.
+ * Returns a status.
+ */
+struct mgwin *
+popbuf(struct buffer *bp, int flags)
+{
+       struct mgwin    *wp;
+
+       if (bp->b_nwnd == 0) {  /* Not on screen yet.    */
+               /* 
+                * Pick a window for a pop-up.
+                * If only one window, split the screen.
+                * Flag the new window as ephemeral
+                */
+               if (wheadp->w_wndp == NULL &&
+                   splitwind(FFOTHARG, flags) == FALSE)
+                       return (NULL);
+
+               /*
+                * Pick the uppermost window that isn't
+                * the current window. An LRU algorithm
+                * might be better. Return a pointer, or NULL on error. 
+                */
+               wp = wheadp;
+
+               while (wp != NULL && wp == curwp)
+                       wp = wp->w_wndp;
+       } else
+               for (wp = wheadp; wp != NULL; wp = wp->w_wndp)
+                       if (wp->w_bufp == bp) {
+                               wp->w_rflag |= WFFULL | WFFRAME;
+                               return (wp);
+                       }
+       if (showbuffer(bp, wp, WFFULL) != TRUE)
+               return (NULL);
+       return (wp);
+}
+
+/*
+ * Insert another buffer at dot.  Very useful.
+ */
+/* ARGSUSED */
+int
+bufferinsert(int f, int n)
+{
+       struct buffer *bp;
+       struct line   *clp;
+       int     clo, nline;
+       char    bufn[NBUFN], *bufp;
+
+       /* Get buffer to use from user */
+       if (curbp->b_altb != NULL)
+               bufp = eread("Insert buffer: (default %s) ", bufn, NBUFN,
+                   EFNUL | EFNEW | EFBUF, curbp->b_altb->b_bname);
+       else
+               bufp = eread("Insert buffer: ", bufn, NBUFN, EFNEW | EFBUF);
+       if (bufp == NULL)
+               return (ABORT);
+       if (bufp[0] == '\0' && curbp->b_altb != NULL)
+               bp = curbp->b_altb;
+       else if ((bp = bfind(bufn, FALSE)) == NULL)
+               return (FALSE);
+
+       if (bp == curbp) {
+               ewprintf("Cannot insert buffer into self");
+               return (FALSE);
+       }
+       /* insert the buffer */
+       nline = 0;
+       clp = bfirstlp(bp);
+       for (;;) {
+               for (clo = 0; clo < llength(clp); clo++)
+                       if (linsert(1, lgetc(clp, clo)) == FALSE)
+                               return (FALSE);
+               if ((clp = lforw(clp)) == bp->b_headp)
+                       break;
+               if (newline(FFRAND, 1) == FALSE)        /* fake newline */
+                       return (FALSE);
+               nline++;
+       }
+       if (nline == 1)
+               ewprintf("[Inserted 1 line]");
+       else
+               ewprintf("[Inserted %d lines]", nline);
+
+       clp = curwp->w_linep;           /* cosmetic adjustment  */
+       if (curwp->w_dotp == clp) {     /* for offscreen insert */
+               while (nline-- && lback(clp) != curbp->b_headp)
+                       clp = lback(clp);
+               curwp->w_linep = clp;   /* adjust framing.      */
+               curwp->w_rflag |= WFFULL;
+       }
+       return (TRUE);
+}
+
+/*
+ * Turn off the dirty bit on this buffer.
+ */
+/* ARGSUSED */
+int
+notmodified(int f, int n)
+{
+       struct mgwin *wp;
+
+       curbp->b_flag &= ~BFCHG;
+       wp = wheadp;            /* Update mode lines.    */
+       while (wp != NULL) {
+               if (wp->w_bufp == curbp)
+                       wp->w_rflag |= WFMODE;
+               wp = wp->w_wndp;
+       }
+       ewprintf("Modification-flag cleared");
+       return (TRUE);
+}
+
+/*
+ * Popbuf and set all windows to top of buffer.
+ */
+int
+popbuftop(struct buffer *bp, int flags)
+{
+       struct mgwin *wp;
+
+       bp->b_dotp = bfirstlp(bp);
+       bp->b_doto = 0;
+       if (bp->b_nwnd != 0) {
+               for (wp = wheadp; wp != NULL; wp = wp->w_wndp)
+                       if (wp->w_bufp == bp) {
+                               wp->w_dotp = bp->b_dotp;
+                               wp->w_doto = 0;
+                               wp->w_rflag |= WFFULL;
+                       }
+       }
+       return (popbuf(bp, flags) != NULL);
+}
+
+/*
+ * Return the working directory for the current buffer, terminated
+ * with a '/'. First, try to extract it from the current buffer's
+ * filename. If that fails, use global cwd.
+ */
+int
+getbufcwd(char *path, size_t plen)
+{
+       char cwd[NFILEN];
+
+       if (plen == 0)
+               return (FALSE);
+
+       if (globalwd == FALSE && curbp->b_cwd[0] != '\0') {
+               (void)strlcpy(path, curbp->b_cwd, plen);
+       } else {
+               if (getcwdir(cwd, sizeof(cwd)) == FALSE)
+                       goto error;
+               (void)strlcpy(path, cwd, plen);
+       }
+       return (TRUE);
+error:
+       path[0] = '\0';
+       return (FALSE);
+}
+
+/*
+ * Ensures a buffer has not been modified elsewhere; e.g. on disk.
+ * Prompt the user if it has.
+ * Returns TRUE if it has NOT (i.e. buffer is ok to edit).
+ * FALSE or ABORT otherwise
+ */
+int
+checkdirty(struct buffer *bp)
+{
+       int s;
+       
+       if ((bp->b_flag & (BFDIRTY | BFIGNDIRTY)) == BFDIRTY) {
+               if ((s = eyorn("File changed on disk; really edit the buffer"))
+                   != TRUE)
+                       return (s);
+               bp->b_flag &= ~BFDIRTY;
+               bp->b_flag |= BFIGNDIRTY;
+       }
+
+       return (TRUE);
+}
+       
diff --git a/mg/chrdef.h b/mg/chrdef.h
new file mode 100644 (file)
index 0000000..ed94098
--- /dev/null
@@ -0,0 +1,81 @@
+/*     $OpenBSD: chrdef.h,v 1.7 2005/06/14 18:14:40 kjell Exp $        */
+
+/* This file is in the public domain. */
+
+/*
+ * sys/default/chardef.h: character set specific #defines for Mg 2a
+ * Warning: System specific ones exist
+ */
+
+/*
+ * Casting should be at least as efficient as anding with 0xff,
+ * and won't have the size problems.  Override in sysdef.h if no
+ * unsigned char type.
+ */
+#define        CHARMASK(c)     ((unsigned char) (c))
+
+/*
+ * These flags, and the macros below them,
+ * make up a do-it-yourself set of "ctype" macros that
+ * understand the DEC multinational set, and let me ask
+ * a slightly different set of questions.
+ */
+#define _MG_W  0x01            /* Word.                         */
+#define _MG_U  0x02            /* Upper case letter.            */
+#define _MG_L  0x04            /* Lower case letter.            */
+#define _MG_C  0x08            /* Control.                      */
+#define _MG_P  0x10            /* end of sentence punctuation   */
+#define        _MG_D   0x20            /* is decimal digit              */
+
+#define ISWORD(c)      ((cinfo[CHARMASK(c)]&_MG_W)!=0)
+#define ISCTRL(c)      ((cinfo[CHARMASK(c)]&_MG_C)!=0)
+#define ISUPPER(c)     ((cinfo[CHARMASK(c)]&_MG_U)!=0)
+#define ISLOWER(c)     ((cinfo[CHARMASK(c)]&_MG_L)!=0)
+#define ISEOSP(c)      ((cinfo[CHARMASK(c)]&_MG_P)!=0)
+#define        ISDIGIT(c)      ((cinfo[CHARMASK(c)]&_MG_D)!=0)
+#define TOUPPER(c)     ((c)-0x20)
+#define TOLOWER(c)     ((c)+0x20)
+
+/*
+ * Generally useful thing for chars
+ */
+#define CCHR(x)                ((x) ^ 0x40)    /* CCHR('?') == DEL */
+
+#ifndef        METACH
+#define        METACH          CCHR('[')
+#endif
+
+#ifdef XKEYS
+#define        K00             256
+#define        K01             257
+#define        K02             258
+#define        K03             259
+#define        K04             260
+#define        K05             261
+#define        K06             262
+#define        K07             263
+#define        K08             264
+#define        K09             265
+#define        K0A             266
+#define        K0B             267
+#define        K0C             268
+#define        K0D             269
+#define        K0E             270
+#define        K0F             271
+#define        K10             272
+#define        K11             273
+#define        K12             274
+#define        K13             275
+#define        K14             276
+#define        K15             277
+#define        K16             278
+#define        K17             279
+#define        K18             280
+#define        K19             281
+#define        K1A             282
+#define        K1B             283
+#define        K1C             284
+#define        K1D             285
+#define        K1E             286
+#define        K1F             287
+#endif
diff --git a/mg/cinfo.c b/mg/cinfo.c
new file mode 100644 (file)
index 0000000..f568897
--- /dev/null
@@ -0,0 +1,154 @@
+/*     $OpenBSD: cinfo.c,v 1.16 2011/11/28 04:41:39 lum Exp $  */
+
+/* This file is in the public domain. */
+
+/*
+ *             Character class tables.
+ * Do it yourself character classification
+ * macros, that understand the multinational character set,
+ * and let me ask some questions the standard macros (in
+ * ctype.h) don't let you ask.
+ */
+#include       "def.h"
+
+/*
+ * This table, indexed by a character drawn
+ * from the 256 member character set, is used by my
+ * own character type macros to answer questions about the
+ * type of a character. It handles the full multinational
+ * character set, and lets me ask some questions that the
+ * standard "ctype" macros cannot ask.
+ */
+/*
+ * Due to incompatible behaviour between "standard" emacs and
+ * ctags word traversing, '_' character's value is changed on 
+ * the fly in ctags mode, hence non-const.
+ */
+char cinfo[256] = {
+       _MG_C, _MG_C, _MG_C, _MG_C,                                   /* 0x0X */
+       _MG_C, _MG_C, _MG_C, _MG_C,
+       _MG_C, _MG_C, _MG_C, _MG_C,
+       _MG_C, _MG_C, _MG_C, _MG_C,
+       _MG_C, _MG_C, _MG_C, _MG_C,                                   /* 0x1X */
+       _MG_C, _MG_C, _MG_C, _MG_C,
+       _MG_C, _MG_C, _MG_C, _MG_C,
+       _MG_C, _MG_C, _MG_C, _MG_C,
+       0, _MG_P, 0, 0,                                               /* 0x2X */
+       _MG_W, _MG_W, 0, _MG_W,
+       0, 0, 0, 0,
+       0, 0, _MG_P, 0,
+       _MG_D | _MG_W, _MG_D | _MG_W, _MG_D | _MG_W, _MG_D | _MG_W,   /* 0x3X */
+       _MG_D | _MG_W, _MG_D | _MG_W, _MG_D | _MG_W, _MG_D | _MG_W,
+       _MG_D | _MG_W, _MG_D | _MG_W, 0, 0,
+       0, 0, 0, _MG_P,
+       0, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,               /* 0x4X */
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,   /* 0x5X */
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, 0,
+       0, 0, 0, 0,
+       0, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,               /* 0x6X */
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,   /* 0x7X */
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, 0,
+       0, 0, 0, _MG_C,
+       0, 0, 0, 0,                                                   /* 0x8X */
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+       0, 0, 0, 0,                                                   /* 0x9X */
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+       0, 0, 0, 0,                                                   /* 0xAX */
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+       0, 0, 0, 0,                                                   /* 0xBX */
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,   /* 0xCX */
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,
+       0, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,               /* 0xDX */
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,
+       _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,
+       _MG_U | _MG_W, _MG_U | _MG_W, 0, _MG_W,
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,   /* 0xEX */
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
+       0, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,               /* 0xFX */
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
+       _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
+       _MG_L | _MG_W, _MG_L | _MG_W, 0, 0
+};
+
+/*
+ * Find the name of a keystroke.  Needs to be changed to handle 8-bit printing
+ * characters and function keys better.         Returns a pointer to the terminating
+ * '\0'.  Returns NULL on failure.
+ */
+char *
+getkeyname(char *cp, size_t len, int k)
+{
+       const char      *np;
+       size_t           copied;
+
+       if (k < 0)
+               k = CHARMASK(k);        /* sign extended char */
+       switch (k) {
+       case CCHR('@'):
+               np = "C-SPC";
+               break;
+       case CCHR('I'):
+               np = "TAB";
+               break;
+       case CCHR('M'):
+               np = "RET";
+               break;
+       case CCHR('['):
+               np = "ESC";
+               break;
+       case ' ':
+               np = "SPC";
+               break;          /* yuck again */
+       case CCHR('?'):
+               np = "DEL";
+               break;
+       default:
+#ifdef FKEYS
+               if (k >= KFIRST && k <= KLAST &&
+                   (np = keystrings[k - KFIRST]) != NULL)
+                       break;
+#endif
+               if (k > CCHR('?')) {
+                       *cp++ = '0';
+                       *cp++ = ((k >> 6) & 7) + '0';
+                       *cp++ = ((k >> 3) & 7) + '0';
+                       *cp++ = (k & 7) + '0';
+                       *cp = '\0';
+                       return (cp);
+               } else if (k < ' ') {
+                       *cp++ = 'C';
+                       *cp++ = '-';
+                       k = CCHR(k);
+                       if (ISUPPER(k))
+                               k = TOLOWER(k);
+               }
+               *cp++ = k;
+               *cp = '\0';
+               return (cp);
+       }
+       copied = strlcpy(cp, np, len);
+       if (copied >= len)
+               copied = len - 1;
+       return (cp + copied);
+}
diff --git a/mg/cmode.c b/mg/cmode.c
new file mode 100644 (file)
index 0000000..674d89d
--- /dev/null
@@ -0,0 +1,528 @@
+/* $OpenBSD: cmode.c,v 1.8 2012/05/18 02:13:44 lum Exp $ */
+/*
+ * This file is in the public domain.
+ *
+ * Author: Kjell Wooding <kjell@openbsd.org>
+ */
+
+/*
+ * Implement an non-irritating KNF-compliant mode for editing
+ * C code.
+ */
+#include <ctype.h>
+
+#include "def.h"
+#include "kbd.h"
+#include "funmap.h"
+
+/* Pull in from modes.c */
+extern int changemode(int, int, char *);
+
+static int cc_strip_trailp = TRUE;     /* Delete Trailing space? */
+static int cc_basic_indent = 8;                /* Basic Indent multiple */
+static int cc_cont_indent = 4;         /* Continued line indent */
+static int cc_colon_indent = -8;       /* Label / case indent */
+
+static int getmatch(int, int);
+static int getindent(const struct line *, int *);
+static int in_whitespace(struct line *, int);
+static int findcolpos(const struct buffer *, const struct line *, int);
+static struct line *findnonblank(struct line *);
+static int isnonblank(const struct line *, int);
+
+void cmode_init(void);
+int cc_comment(int, int);
+
+/* Keymaps */
+
+static PF cmode_brace[] = {
+       cc_brace,       /* } */
+};
+
+static PF cmode_cCP[] = {
+       compile,                /* C-c P */
+};
+
+
+static PF cmode_cc[] = {
+       NULL,           /* ^C */
+       rescan,         /* ^D */
+       rescan,         /* ^E */
+       rescan,         /* ^F */
+       rescan,         /* ^G */
+       rescan,         /* ^H */
+       cc_tab,         /* ^I */
+       rescan,         /* ^J */
+       rescan,         /* ^K */
+       rescan,         /* ^L */
+       cc_lfindent,    /* ^M */
+};
+
+static PF cmode_spec[] = {
+       cc_char,        /* : */
+};
+
+static struct KEYMAPE (1 + IMAPEXT) cmode_cmap = {
+       1,
+       1 + IMAPEXT,
+       rescan,
+       {
+               { 'P', 'P', cmode_cCP, NULL }
+       }
+};
+
+static struct KEYMAPE (3 + IMAPEXT) cmodemap = {
+       3,
+       3 + IMAPEXT,
+       rescan,
+       {
+               { CCHR('C'), CCHR('M'), cmode_cc, (KEYMAP *) &cmode_cmap },
+               { ':', ':', cmode_spec, NULL },
+               { '}', '}', cmode_brace, NULL }
+       }
+};
+
+/* Funtion, Mode hooks */
+
+void
+cmode_init(void)
+{
+       funmap_add(cmode, "c-mode");
+       funmap_add(cc_char, "c-handle-special-char");
+       funmap_add(cc_brace, "c-handle-special-brace");
+       funmap_add(cc_tab, "c-tab-or-indent");
+       funmap_add(cc_indent, "c-indent");
+       funmap_add(cc_lfindent, "c-indent-and-newline");
+       maps_add((KEYMAP *)&cmodemap, "c");
+}
+
+/*
+ * Enable/toggle c-mode
+ */
+int
+cmode(int f, int n)
+{
+       return(changemode(f, n, "c"));
+}
+
+/*
+ * Handle special C character - selfinsert then indent.
+ */
+int
+cc_char(int f, int n)
+{
+       if (n < 0)
+               return (FALSE);
+       if (selfinsert(FFRAND, n) == FALSE)
+               return (FALSE);
+       return (cc_indent(FFRAND, n));
+}
+
+/*
+ * Handle special C character - selfinsert then indent.
+ */
+int
+cc_brace(int f, int n)
+{
+       if (n < 0)
+               return (FALSE);
+       if (showmatch(FFRAND, 1) == FALSE)
+               return (FALSE);
+       return (cc_indent(FFRAND, n));
+}
+
+
+/*
+ * If we are in the whitespace at the beginning of the line,
+ * simply act as a regular tab. If we are not, indent
+ * current line according to whitespace rules.
+ */
+int
+cc_tab(int f, int n)
+{
+       int inwhitep = FALSE;   /* In leading whitespace? */
+       
+       inwhitep = in_whitespace(curwp->w_dotp, llength(curwp->w_dotp));
+
+       /* If empty line, or in whitespace */
+       if (llength(curwp->w_dotp) == 0 || inwhitep)
+               return (selfinsert(f, n));
+
+       return (cc_indent(FFRAND, 1));
+}
+
+/*
+ * Attempt to indent current line according to KNF rules.
+ */
+int
+cc_indent(int f, int n)
+{
+       int pi, mi;                     /* Previous indents */
+       int ci, dci;                    /* current indent, don't care */
+       struct line *lp;
+       int ret;
+       
+       if (n < 0)
+               return (FALSE);
+
+       undo_boundary_enable(FFRAND, 0);
+       if (cc_strip_trailp)
+               deltrailwhite(FFRAND, 1);
+
+       /*
+        * Search backwards for a non-blank, non-preprocessor,
+        * non-comment line
+        */
+
+       lp = findnonblank(curwp->w_dotp);
+
+       pi = getindent(lp, &mi);
+
+       /* Strip leading space on current line */
+       delleadwhite(FFRAND, 1);
+       /* current indent is computed only to current position */
+       dci = getindent(curwp->w_dotp, &ci);
+       
+       if (pi + ci < 0)
+               ret = indent(FFOTHARG, 0);
+       else
+               ret = indent(FFOTHARG, pi + ci);
+       
+       undo_boundary_enable(FFRAND, 1);
+       
+       return (ret);
+}
+
+/*
+ * Indent-and-newline (technically, newline then indent)
+ */
+int
+cc_lfindent(int f, int n)
+{
+       if (n < 0)
+               return (FALSE);
+       if (newline(FFRAND, 1) == FALSE)
+               return (FALSE);
+       return (cc_indent(FFRAND, n));
+}
+
+/*
+ * Get the level of indention after line lp is processed
+ * Note getindent has two returns:
+ * curi = value if indenting current line.
+ * return value = value affecting subsequent lines.
+ */
+static int
+getindent(const struct line *lp, int *curi)
+{
+       int lo, co;             /* leading space,  current offset*/
+       int nicol = 0;          /* position count */
+       int ccol = 0;           /* current column */
+       int c = '\0';           /* current char */
+       int newind = 0;         /* new index value */
+       int stringp = FALSE;    /* in string? */
+       int escp = FALSE;       /* Escape char? */
+       int lastc = '\0';       /* Last matched string delimeter */
+       int nparen = 0;         /* paren count */
+       int obrace = 0;         /* open brace count */
+       int cbrace = 0;         /* close brace count */
+       int contp = FALSE;      /* Continue? */
+       int firstnwsp = FALSE;  /* First nonspace encountered? */
+       int colonp = FALSE;     /* Did we see a colon? */
+       int questionp = FALSE;  /* Did we see a question mark? */
+       int slashp = FALSE;     /* Slash? */
+       int astp = FALSE;       /* Asterisk? */
+       int cpos = -1;          /* comment position */
+       int cppp  = FALSE;      /* Preprocessor command? */
+       
+       *curi = 0;
+
+       /* Compute leading space */
+       for (lo = 0; lo < llength(lp); lo++) {
+               if (!isspace(c = lgetc(lp, lo)))
+                       break;
+               if (c == '\t'
+#ifdef NOTAB
+                   && !(curbp->b_flag & BFNOTAB)
+#endif /* NOTAB */
+                   ) {
+                       nicol |= 0x07;
+               }
+               nicol++;
+       }
+
+       /* If last line was blank, choose 0 */
+       if (lo == llength(lp))
+               nicol = 0;
+
+       newind = 0;
+       ccol = nicol;                   /* current column */
+       /* Compute modifiers */
+       for (co = lo; co < llength(lp); co++) {
+               c = lgetc(lp, co);
+               /* We have a non-whitespace char */
+               if (!firstnwsp && !isspace(c)) {
+                       contp = TRUE;
+                       if (c == '#')
+                               cppp = TRUE;
+                       firstnwsp = TRUE; 
+               }
+               if (c == '\\')
+                       escp = !escp;
+               else if (stringp) {
+                       if (!escp && (c == '"' || c == '\'')) {
+                               /* unescaped string char */
+                               if (getmatch(c, lastc))
+                                       stringp = FALSE;
+                       }
+               } else if (c == '"' || c == '\'') {
+                       stringp = TRUE;
+                       lastc = c;
+               } else if (c == '(') {
+                       nparen++;
+               } else if (c == ')') {
+                       nparen--;
+               } else if (c == '{') {
+                       obrace++;
+                       firstnwsp = FALSE;
+                       contp = FALSE;
+               } else if (c == '}') {
+                       cbrace++;
+               } else if (c == '?') {
+                       questionp = TRUE;
+               } else if (c == ':') {
+                       /* ignore (foo ? bar : baz) construct */
+                       if (!questionp)
+                               colonp = TRUE;
+               } else if (c == ';') {
+                       if (nparen > 0)
+                               contp = FALSE;
+               } else if (c == '/') {
+                       /* first nonwhitespace? -> indent */
+                       if (firstnwsp) {
+                               /* If previous char asterisk -> close */
+                               if (astp)
+                                       cpos = -1;
+                               else
+                                       slashp = TRUE;
+                       }
+               } else if (c == '*') {
+                       /* If previous char slash -> open */
+                       if (slashp)
+                               cpos = co;
+                       else
+                               astp = TRUE;
+               } else if (firstnwsp) {
+                       firstnwsp = FALSE;
+               }
+
+               /* Reset matches that apply to next character only */
+               if (c != '\\')
+                       escp = FALSE;
+               if (c != '*')
+                       astp = FALSE;
+               if (c != '/')
+                       slashp = FALSE;
+       }
+       /*
+        * If not terminated with a semicolon, and brace or paren open.
+        * we continue
+        */
+       if (colonp) {
+               *curi += cc_colon_indent;
+               newind -= cc_colon_indent;
+       }
+
+       *curi -= (cbrace) * cc_basic_indent;
+       newind += obrace * cc_basic_indent;
+
+       if (nparen < 0)
+               newind -= cc_cont_indent;
+       else if (nparen > 0)
+               newind += cc_cont_indent;
+
+       *curi += nicol;
+
+       /* Ignore preprocessor. Otherwise, add current column */
+       if (cppp) {
+               newind = nicol;
+               *curi = 0;
+       } else {
+               newind += nicol;
+       }
+
+       if (cpos != -1)
+               newind = findcolpos(curbp, lp, cpos);
+
+       return (newind);
+}
+
+/*
+ * Given a delimeter and its purported mate, tell us if they
+ * match.
+ */
+static int
+getmatch(int c, int mc)
+{
+       int match = FALSE;
+
+       switch (c) {
+       case '"':
+               match = (mc == '"');
+               break;
+       case '\'':
+               match = (mc == '\'');
+               break;
+       case '(':
+               match = (mc == ')');
+               break;
+       case '[':
+               match = (mc == ']');
+               break;
+       case '{':
+               match = (mc == '}');
+               break;
+       }
+
+       return (match);
+}
+
+static int
+in_whitespace(struct line *lp, int len)
+{
+       int lo;
+       int inwhitep = FALSE;
+
+       for (lo = 0; lo < len; lo++) {
+               if (!isspace(lgetc(lp, lo)))
+                       break;
+               if (lo == len - 1)
+                       inwhitep = TRUE;
+       }
+
+       return (inwhitep);
+}
+
+
+/* convert a line/offset pair to a column position (for indenting) */
+static int
+findcolpos(const struct buffer *bp, const struct line *lp, int lo)
+{
+       int     col, i, c;
+       char tmp[5];
+
+       /* determine column */
+       col = 0;
+
+       for (i = 0; i < lo; ++i) {
+               c = lgetc(lp, i);
+               if (c == '\t'
+#ifdef NOTAB
+                   && !(bp->b_flag & BFNOTAB)
+#endif /* NOTAB */
+                       ) {
+                       col |= 0x07;
+                       col++;
+               } else if (ISCTRL(c) != FALSE)
+                       col += 2;
+               else if (isprint(c)) {
+                       col++;
+               } else {
+                       col += snprintf(tmp, sizeof(tmp), "\\%o", c);
+               }
+
+       }
+       return (col);
+}
+
+/*
+ * Find a non-blank line, searching backwards from the supplied line pointer.
+ * For C, nonblank is non-preprocessor, non C++, and accounts
+ * for complete C-style comments.
+ */
+static struct line *
+findnonblank(struct line *lp)
+{
+       int lo;
+       int nonblankp = FALSE;
+       int commentp = FALSE;
+       int slashp;
+       int astp;
+       int c;
+
+       while (lback(lp) != curbp->b_headp && (commentp || !nonblankp)) {
+               lp = lback(lp);
+               slashp = FALSE;
+               astp = FALSE;
+
+               /* Potential nonblank? */
+               nonblankp = isnonblank(lp, llength(lp));
+
+               /*
+                * Search from end, removing complete C-style
+                * comments. If one is found, ignore it and
+                * test for nonblankness from where it starts.
+                */
+               slashp = FALSE;
+               /* Scan backwards from end to find C-style comment */
+               for (lo = llength(lp) - 1; lo >= 0; lo--) {
+                       if (!isspace(c = lgetc(lp, lo))) {
+                               if (commentp) { /* find comment "open" */
+                                       if (c == '*')
+                                               astp = TRUE;
+                                       else if (astp && c == '/') {
+                                               commentp = FALSE;
+                                               /* whitespace to here? */
+                                               nonblankp = isnonblank(lp, lo);
+                                       }
+                               } else { /* find comment "close" */
+                                       if (c == '/')
+                                               slashp = TRUE;
+                                       else if (slashp && c == '*')
+                                               /* found a comment */
+                                               commentp = TRUE;
+                               }
+                       }
+               }
+       }
+
+       /* Rewound to start of file? */
+       if (lback(lp) == curbp->b_headp && !nonblankp)
+               return (curbp->b_headp);
+
+       return (lp);
+}
+
+/*
+ * Given a line, scan forward to 'omax' and determine if we
+ * are all C whitespace.
+ * Note that preprocessor directives and C++-style comments
+ * count as whitespace. C-style comments do not, and must
+ * be handled elsewhere.
+ */
+static int
+isnonblank(const struct line *lp, int omax)
+{
+       int nonblankp = FALSE;          /* Return value */
+       int slashp = FALSE;             /* Encountered slash */
+       int lo;                         /* Loop index */
+       int c;                          /* char being read */
+
+       /* Scan from front for preprocessor, C++ comments */
+       for (lo = 0; lo < omax; lo++) {
+               if (!isspace(c = lgetc(lp, lo))) {
+                       /* Possible nonblank line */
+                       nonblankp = TRUE;
+                       /* skip // and # starts */
+                       if (c == '#' || (slashp && c == '/')) {
+                               nonblankp = FALSE;
+                               break;
+                       } else if (!slashp && c == '/') {
+                               slashp = TRUE;
+                               continue;
+                       }
+               }
+               slashp = FALSE;
+       }
+       return (nonblankp);
+}
diff --git a/mg/cscope.c b/mg/cscope.c
new file mode 100644 (file)
index 0000000..9a8db5e
--- /dev/null
@@ -0,0 +1,609 @@
+/*     $OpenBSD: cscope.c,v 1.3 2012/07/02 08:08:31 lum Exp $  */
+
+/*
+ * This file is in the public domain.
+ *
+ * Author: Sunil Nimmagadda <sunil@sunilnimmagadda.com>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "def.h"
+
+#define CSSYMBOL      0
+#define CSDEFINITION  1
+#define CSCALLEDFUNCS 2
+#define CSCALLERFUNCS 3
+#define CSTEXT        4
+#define CSEGREP       6
+#define CSFINDFILE    7
+#define CSINCLUDES    8
+
+struct cstokens {
+       const char *fname;
+       const char *function;
+       const char *lineno;
+       const char *pattern;
+};
+
+struct csmatch {
+       TAILQ_ENTRY(csmatch) entry;
+       int lineno;
+};
+
+struct csrecord {
+       TAILQ_ENTRY(csrecord) entry;
+       char *filename;
+       TAILQ_HEAD(matches, csmatch) matches;
+};
+
+static TAILQ_HEAD(csrecords, csrecord) csrecords = TAILQ_HEAD_INITIALIZER(csrecords);
+static struct csrecord *addentryr;
+static struct csrecord *currecord;
+static struct csmatch  *curmatch;
+static const char      *addentryfn;
+static const char      *csprompt[] = {
+       "Find this symbol: ",
+       "Find this global definition: ",
+       "Find functions called by this function: ",
+       "Find functions calling this function: ",
+       "Find this text string: ",
+       "Change this text string: ",
+       "Find this egrep pattern: ",
+       "Find this file: ",
+       "Find files #including this file: "
+};
+
+static int  addentry(struct buffer *, char *);
+static void csflush(void);
+static int  do_cscope(int);
+static int  csexists(const char *);
+static int  getattr(char *, struct cstokens *);
+static int  jumptomatch(void);
+static void prettyprint(struct buffer *, struct cstokens *);
+static const char *ltrim(const char *);
+
+/*
+ * Find this symbol. Bound to C-c s s
+ */
+/* ARGSUSED */
+int
+cssymbol(int f, int n)
+{
+       return (do_cscope(CSSYMBOL));
+}
+
+/*
+ * Find this global definition. Bound to C-c s d
+ */
+/* ARGSUSED */int
+csdefinition(int f, int n)
+{
+       return (do_cscope(CSDEFINITION));
+}
+
+/*
+ * Find functions called by this function. Bound to C-c s l
+ */
+/* ARGSUSED */
+int
+csfuncalled(int f, int n)
+{
+       return (do_cscope(CSCALLEDFUNCS));
+}
+
+/*
+ * Find functions calling this function. Bound to C-c s c
+ */
+/* ARGSUSED */
+int
+cscallerfuncs(int f, int n)
+{
+       return (do_cscope(CSCALLERFUNCS));
+}
+
+/*
+ * Find this text. Bound to C-c s t
+ */
+/* ARGSUSED */
+int
+csfindtext(int f, int n)
+{
+       return (do_cscope(CSTEXT));
+}
+
+/*
+ * Find this egrep pattern. Bound to C-c s e
+ */
+/* ARGSUSED */
+int
+csegrep(int f, int n)
+{
+       return (do_cscope(CSEGREP));
+}
+
+/*
+ * Find this file. Bound to C-c s f
+ */
+/* ARGSUSED */
+int
+csfindfile(int f, int n)
+{
+       return (do_cscope(CSFINDFILE));
+}
+
+/*
+ * Find files #including this file. Bound to C-c s i
+ */
+/* ARGSUSED */
+int
+csfindinc(int f, int n)
+{
+       return (do_cscope(CSINCLUDES));
+}
+
+/*
+ * Create list of files to index in the given directory
+ * using cscope-indexer.
+ */
+/* ARGSUSED */
+int
+cscreatelist(int f, int n)
+{
+       struct buffer *bp;
+       struct stat sb;
+       FILE *fpipe;
+       char dir[NFILEN], cmd[BUFSIZ], title[BUFSIZ], *line, *bufp;
+       size_t len;
+       int clen;
+       
+       if (getbufcwd(dir, sizeof(dir)) == FALSE)
+               dir[0] = '\0';
+       
+       bufp = eread("Index files in directory: ", dir, 
+           sizeof(dir), EFCR | EFDEF | EFNEW | EFNUL);
+       
+       if (bufp == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+               
+       if (stat(dir, &sb) == -1) {
+               ewprintf("stat: %s", strerror(errno));
+               return (FALSE);
+       } else if (S_ISDIR(sb.st_mode) == 0) {
+               ewprintf("%s: Not a directory", dir);
+               return (FALSE);
+       }
+       
+       if (csexists("cscope-indexer") == FALSE) {
+               ewprintf("no such file or directory, cscope-indexer");
+               return (FALSE);
+       }
+       
+       clen = snprintf(cmd, sizeof(cmd), "cscope-indexer -v %s", dir);
+       if (clen < 0 || clen >= sizeof(cmd))
+               return (FALSE);
+
+       if ((fpipe = popen(cmd, "r")) == NULL) {
+               ewprintf("problem opening pipe");
+               return (FALSE);
+       }
+       
+       bp = bfind("*cscope*", TRUE);
+       if (bclear(bp) != TRUE)
+               return (FALSE);
+       bp->b_flag |= BFREADONLY;
+
+       clen = snprintf(title, sizeof(title), "%s%s",
+           "Creating cscope file list 'cscope.files' in: ", dir);
+       if (clen < 0 || clen >= sizeof(title))
+               return (FALSE);
+       addline(bp, title);
+       addline(bp, "");
+       /* All lines are NUL terminated */
+       while ((line = fgetln(fpipe, &len)) != NULL) {
+               line[len - 1] = '\0';
+               addline(bp, line);
+       }
+       pclose(fpipe);
+       return (popbuftop(bp, WNONE));  
+}
+
+/*
+ * Next Symbol. Bound to C-c s n
+ */
+/* ARGSUSED */
+int
+csnextmatch(int f, int n)
+{
+       struct csrecord *r;
+       struct csmatch *m;
+       
+       if (curmatch == NULL) {
+               if ((r = TAILQ_FIRST(&csrecords)) == NULL) {
+                       ewprintf("The *cscope* buffer does not exist yet");
+                       return (FALSE);
+               }
+               currecord = r;
+               curmatch = TAILQ_FIRST(&r->matches);
+       } else {
+               m = TAILQ_NEXT(curmatch, entry);
+               if (m == NULL) {
+                       r = TAILQ_NEXT(currecord, entry);
+                       if (r == NULL) {
+                               ewprintf("The end of *cscope* buffer has been"
+                                   " reached");
+                               return (FALSE);
+                       } else {
+                               currecord = r;
+                               curmatch = TAILQ_FIRST(&currecord->matches);
+                       }
+               } else
+                       curmatch = m;
+       }
+       return (jumptomatch());
+}
+
+/*
+ * Previous Symbol. Bound to C-c s p
+ */
+/* ARGSUSED */
+int
+csprevmatch(int f, int n)
+{
+       struct csmatch *m;
+       struct csrecord *r;
+
+       if (curmatch == NULL)
+               return (FALSE);
+       else {
+               m  = TAILQ_PREV(curmatch, matches, entry);
+               if (m)
+                       curmatch = m;
+               else {
+                       r = TAILQ_PREV(currecord, csrecords, entry);
+                       if (r == NULL) {
+                               ewprintf("The beginning of *cscope* buffer has"
+                                   " been reached");
+                               return (FALSE);
+                       } else {
+                               currecord = r;
+                               curmatch = TAILQ_LAST(&currecord->matches,
+                                   matches);
+                       }
+               }
+       }
+       return (jumptomatch());
+}
+
+/*
+ * Next file.
+ */
+int
+csnextfile(int f, int n)
+{
+       struct csrecord *r;
+       
+       if (curmatch == NULL) {
+               if ((r = TAILQ_FIRST(&csrecords)) == NULL) {
+                       ewprintf("The *cscope* buffer does not exist yet");
+                       return (FALSE);
+               }
+
+       } else {
+               if ((r = TAILQ_NEXT(currecord, entry)) == NULL) {
+                       ewprintf("The end of *cscope* buffer has been reached");
+                       return (FALSE);
+               }
+       }
+       currecord = r;
+       curmatch = TAILQ_FIRST(&currecord->matches);
+       return (jumptomatch()); 
+}
+
+/*
+ * Previous file.
+ */
+int
+csprevfile(int f, int n)
+{
+       struct csrecord *r;
+       
+       if (curmatch == NULL) {
+               if ((r = TAILQ_FIRST(&csrecords)) == NULL) {
+                       ewprintf("The *cscope* buffer does not exist yet");
+                       return (FALSE);
+               }
+
+       } else {
+               if ((r = TAILQ_PREV(currecord, csrecords, entry)) == NULL) {
+                       ewprintf("The beginning of *cscope* buffer has been"
+                           " reached");
+                       return (FALSE);
+               }
+       }
+       currecord = r;
+       curmatch = TAILQ_FIRST(&currecord->matches);
+       return (jumptomatch()); 
+}
+
+/*
+ * The current symbol location is extracted from currecord->filename and 
+ * curmatch->lineno. Load the file similar to filevisit and goto the 
+ * lineno recorded.
+ */
+int
+jumptomatch(void)
+{
+       struct buffer *bp;
+       char *adjf;
+       
+       if (curmatch == NULL || currecord == NULL)
+               return (FALSE);
+       adjf = adjustname(currecord->filename, TRUE);
+       if (adjf == NULL)
+               return (FALSE);
+       if ((bp = findbuffer(adjf)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       if (showbuffer(bp, curwp, WFFULL) != TRUE)
+               return (FALSE);
+       if (bp->b_fname[0] == '\0') {
+               if (readin(adjf) != TRUE)
+                       killbuffer(bp);
+       }
+       gotoline(FFARG, curmatch->lineno);
+       return (TRUE);
+       
+}
+
+/*
+ * Ask for the symbol, construct cscope commandline with the symbol
+ * and passed in index. Popen cscope, read the output into *cscope* 
+ * buffer and pop it.
+ */
+int
+do_cscope(int i)
+{
+       struct buffer *bp;
+       FILE *fpipe;
+       char pattern[MAX_TOKEN], cmd[BUFSIZ], title[BUFSIZ];
+       char *p, *buf;
+       int clen, nores = 0;
+       size_t len;
+
+       /* If current buffer isn't a source file just return */
+       if (fnmatch("*.[chy]", curbp->b_fname, 0) != 0) {
+               ewprintf("C-c s not defined");
+               return (FALSE);
+       }
+       
+       if (curtoken(0, 1, pattern) == FALSE)
+               return (FALSE); 
+       p = eread(csprompt[i], pattern, MAX_TOKEN, EFNEW | EFCR | EFDEF);
+       if (p == NULL)
+               return (ABORT);
+       else if (p[0] == '\0')
+               return (FALSE);
+
+       if (csexists("cscope") == FALSE) {
+               ewprintf("no such file or directory, cscope");
+               return (FALSE);
+       }
+       
+       csflush();
+       clen = snprintf(cmd, sizeof(cmd), "cscope -L -%d %s 2>/dev/null",
+           i, pattern);
+       if (clen < 0 || clen >= sizeof(cmd))
+               return (FALSE);
+
+       if ((fpipe = popen(cmd, "r")) == NULL) {
+               ewprintf("problem opening pipe");
+               return (FALSE);
+       }
+       
+       bp = bfind("*cscope*", TRUE);
+       if (bclear(bp) != TRUE)
+               return (FALSE);
+       bp->b_flag |= BFREADONLY;
+
+       clen = snprintf(title, sizeof(title), "%s%s", csprompt[i], pattern);
+       if (clen < 0 || clen >= sizeof(title))
+               return (FALSE);
+       addline(bp, title);
+       addline(bp, "");
+       addline(bp, "-------------------------------------------------------------------------------");
+       /* All lines are NUL terminated */
+       while ((buf = fgetln(fpipe, &len)) != NULL) {
+               buf[len - 1] = '\0';
+               if (addentry(bp, buf) != TRUE)
+                       return (FALSE);
+               nores = 1;
+       }; 
+       pclose(fpipe);
+       addline(bp, "-------------------------------------------------------------------------------");
+       if (nores == 0)
+               ewprintf("No matches were found.");
+       return (popbuftop(bp, WNONE));
+}
+
+/*
+ * For each line read from cscope output, extract the tokens,
+ * add them to list and pretty print a line in *cscope* buffer.
+ */
+int
+addentry(struct buffer *bp, char *csline)
+{
+       struct csrecord *r;
+       struct csmatch *m;
+       struct cstokens t;
+       int lineno;
+       char buf[BUFSIZ];
+       const char *errstr;
+
+       r = NULL;
+       if (getattr(csline, &t) == FALSE)
+               return (FALSE);
+
+       lineno = strtonum(t.lineno, INT_MIN, INT_MAX, &errstr);
+       if (errstr)
+               return (FALSE);
+               
+       if (addentryfn == NULL || strcmp(addentryfn, t.fname) != 0) {
+               if ((r = malloc(sizeof(struct csrecord))) == NULL)
+                       return (FALSE);
+               addentryr = r;
+               if ((r->filename = strndup(t.fname, NFILEN)) == NULL)
+                       goto cleanup;
+               addentryfn = r->filename;
+               TAILQ_INIT(&r->matches);
+               if ((m = malloc(sizeof(struct csmatch))) == NULL)
+                       goto cleanup;
+               m->lineno = lineno;
+               TAILQ_INSERT_TAIL(&r->matches, m, entry);
+               TAILQ_INSERT_TAIL(&csrecords, r, entry);
+               addline(bp, "");
+               if (snprintf(buf, sizeof(buf), "*** %s", t.fname) < 0)
+                       goto cleanup;
+               addline(bp, buf);
+       } else {
+               if ((m = malloc(sizeof(struct csmatch))) == NULL)
+                       goto cleanup;
+               m->lineno = lineno;
+               TAILQ_INSERT_TAIL(&addentryr->matches, m, entry);
+       }
+       prettyprint(bp, &t);
+       return (TRUE);
+cleanup:
+       free(r);
+       return (FALSE);
+}
+
+/*
+ * Cscope line: <filename> <function> <lineno> <pattern>
+ */
+int
+getattr(char *line, struct cstokens *t)
+{
+       char *p;
+
+       if ((p = strchr(line, ' ')) == NULL)
+               return (FALSE);
+       *p++ = '\0';
+       t->fname = line;
+       line = p;
+
+       if ((p = strchr(line, ' ')) == NULL)
+               return (FALSE);
+       *p++ = '\0';
+       t->function = line;
+       line = p;
+
+       if ((p = strchr(line, ' ')) == NULL)
+               return (FALSE);
+       *p++ = '\0';
+       t->lineno = line;
+
+       if (*p == '\0')
+               return (FALSE);
+       t->pattern = p;
+
+       return (TRUE);
+}
+
+void
+prettyprint(struct buffer *bp, struct cstokens *t)
+{
+       char buf[BUFSIZ];
+
+       if (snprintf(buf, sizeof(buf), "%s[%s]\t\t%s",
+           t->function, t->lineno, ltrim(t->pattern)) < 0)
+               return;
+       addline(bp, buf);
+}
+
+const char *
+ltrim(const char *s)
+{
+       while (isblank(*s))
+               s++;
+       return s;
+}
+
+void
+csflush(void)
+{
+       struct csrecord *r;
+       struct csmatch *m;
+       
+       while ((r = TAILQ_FIRST(&csrecords)) != NULL) {
+               free(r->filename);
+               while ((m = TAILQ_FIRST(&r->matches)) != NULL) {
+                       TAILQ_REMOVE(&r->matches, m, entry);
+                       free(m);
+               }
+               TAILQ_REMOVE(&csrecords, r, entry);
+               free(r);
+       }
+       addentryr = NULL;
+       addentryfn = NULL;
+       currecord = NULL;
+       curmatch = NULL;
+}
+
+/*
+ * Check if the cmd exists in $PATH. Split on ":" and iterate through
+ * all paths in $PATH.
+ */
+int
+csexists(const char *cmd)
+{
+       char fname[NFILEN], *dir, *path, *pathc, *tmp;
+       int  cmdlen, dlen;
+
+       /* Special case if prog contains '/' */
+       if (strchr(cmd, '/')) {
+               if (access(cmd, F_OK) == -1)
+                       return (FALSE);
+               else
+                       return (TRUE);
+       }
+       if ((tmp = getenv("PATH")) == NULL)
+               return (FALSE);
+       if ((pathc = path = strndup(tmp, NFILEN)) == NULL) {
+               ewprintf("out of memory");
+               return (FALSE);
+       }
+       cmdlen = strlen(cmd);
+       while ((dir = strsep(&path, ":")) != NULL) {
+               if (*dir == '\0')
+                       *dir = '.';
+
+               dlen = strlen(dir);
+               while (dir[dlen-1] == '/')
+                       dir[--dlen] = '\0';     /* strip trailing '/' */
+
+               if (dlen + 1 + cmdlen >= sizeof(fname))  {
+                       ewprintf("path too long");
+                       goto cleanup;
+               }
+               snprintf(fname, sizeof(fname), "%s/%s", dir, cmd);
+               if(access(fname, F_OK) == 0) {
+                      free(pathc);
+                      return (TRUE);
+              }
+       }
+cleanup:
+       free(pathc);
+       return (FALSE);
+}
diff --git a/mg/def.h b/mg/def.h
new file mode 100644 (file)
index 0000000..1284e94
--- /dev/null
+++ b/mg/def.h
@@ -0,0 +1,707 @@
+/*     $OpenBSD: def.h,v 1.124 2012/06/14 17:21:22 lum Exp $   */
+
+/* This file is in the public domain. */
+
+/*
+ * This file is the general header file for all parts
+ * of the Mg display editor. It contains all of the
+ * general definitions and macros. It also contains some
+ * conditional compilation flags. All of the per-system and
+ * per-terminal definitions are in special header files.
+ */
+
+#include       "sysdef.h"      /* Order is critical.            */
+#include       "ttydef.h"
+#include       "chrdef.h"
+
+typedef int    (*PF)(int, int);        /* generally useful type */
+
+/*
+ * Table sizes, etc.
+ */
+#define NFILEN 1024            /* Length, file name.            */
+#define NBUFN  NFILEN          /* Length, buffer name.          */
+#define NLINE  256             /* Length, line.                 */
+#define PBMODES 4              /* modes per buffer              */
+#define NKBDM  256             /* Length, keyboard macro.       */
+#define NPAT   80              /* Length, pattern.              */
+#define HUGE   1000            /* A rather large number.        */
+#define NSRCH  128             /* Undoable search commands.     */
+#define NXNAME 64              /* Length, extended command.     */
+#define NKNAME 20              /* Length, key names.            */
+#define NTIME  50              /* Length, timestamp string.     */
+/*
+ * Universal.
+ */
+#define FALSE  0               /* False, no, bad, etc.          */
+#define TRUE   1               /* True, yes, good, etc.         */
+#define ABORT  2               /* Death, ^G, abort, etc.        */
+
+#define KCLEAR 2               /* clear echo area               */
+
+/*
+ * These flag bits keep track of
+ * some aspects of the last command. The CFCPCN
+ * flag controls goal column setting. The CFKILL
+ * flag controls the clearing versus appending
+ * of data in the kill buffer.
+ */
+#define CFCPCN 0x0001          /* Last command was C-P, C-N     */
+#define CFKILL 0x0002          /* Last command was a kill       */
+#define CFINS  0x0004          /* Last command was self-insert  */
+
+/*
+ * File I/O.
+ */
+#define FIOSUC 0               /* Success.                      */
+#define FIOFNF 1               /* File not found.               */
+#define FIOEOF 2               /* End of file.                  */
+#define FIOERR 3               /* Error.                        */
+#define FIOLONG 4              /* long line partially read      */
+#define FIODIR 5               /* File is a directory           */
+
+/*
+ * Directory I/O.
+ */
+#define DIOSUC 0               /* Success.                      */
+#define DIOEOF 1               /* End of file.                  */
+#define DIOERR 2               /* Error.                        */
+
+/*
+ * Display colors.
+ */
+#define CNONE  0               /* Unknown color.                */
+#define CTEXT  1               /* Text color.                   */
+#define CMODE  2               /* Mode line color.              */
+
+/*
+ * Flags for keyboard invoked functions.
+ */
+#define FFUNIV         1       /* universal argument            */
+#define FFNEGARG       2       /* negative only argument        */
+#define FFOTHARG       4       /* other argument                */
+#define FFARG          7       /* any argument                  */
+#define FFRAND         8       /* Called by other function      */
+
+/*
+ * Flags for "eread".
+ */
+#define EFFUNC 0x0001          /* Autocomplete functions.       */
+#define EFBUF  0x0002          /* Autocomplete buffers.         */
+#define EFFILE 0x0004          /* " files (maybe someday)       */
+#define EFAUTO 0x0007          /* Some autocompletion on        */
+#define EFNEW  0x0008          /* New prompt.                   */
+#define EFCR   0x0010          /* Echo CR at end; last read.    */
+#define EFDEF  0x0020          /* buffer contains default args  */
+#define EFNUL  0x0040          /* Null Minibuffer OK            */
+
+/*
+ * Direction of insert into kill ring
+ */
+#define KNONE  0x00
+#define KFORW  0x01            /* forward insert into kill ring */
+#define KBACK  0x02            /* Backwards insert into kill ring */
+#define KREG   0x04            /* This is a region-based kill */
+
+#define MAX_TOKEN 64
+
+/*
+ * This structure holds the starting position
+ * (as a line/offset pair) and the number of characters in a
+ * region of a buffer. This makes passing the specification
+ * of a region around a little bit easier.
+ */
+struct region {
+       struct line     *r_linep;       /* Origin line address.          */
+       int              r_offset;      /* Origin line offset.           */
+       int              r_lineno;      /* Origin line number            */
+       RSIZE            r_size;        /* Length in characters.         */
+};
+
+
+/*
+ * All text is kept in circularly linked
+ * lists of "line" structures. These begin at the
+ * header line (which is the blank line beyond the
+ * end of the buffer). This line is pointed to by
+ * the "buffer" structure. Each line contains the number of
+ * bytes in the line (the "used" size), the size
+ * of the text array, and the text. The end of line
+ * is not stored as a byte; it's implied. Future
+ * additions will include update hints, and a
+ * list of marks into the line.
+ */
+struct line {
+       struct line     *l_fp;          /* Link to the next line         */
+       struct line     *l_bp;          /* Link to the previous line     */
+       int              l_size;        /* Allocated size                */
+       int              l_used;        /* Used size                     */
+       char            *l_text;        /* Content of the line           */
+};
+
+/*
+ * The rationale behind these macros is that you
+ * could (with some editing, like changing the type of a line
+ * link from a "struct line *" to a "REFLINE", and fixing the commands
+ * like file reading that break the rules) change the actual
+ * storage representation of lines to use something fancy on
+ * machines with small address spaces.
+ */
+#define lforw(lp)      ((lp)->l_fp)
+#define lback(lp)      ((lp)->l_bp)
+#define lgetc(lp, n)   (CHARMASK((lp)->l_text[(n)]))
+#define lputc(lp, n, c) ((lp)->l_text[(n)]=(c))
+#define llength(lp)    ((lp)->l_used)
+#define ltext(lp)      ((lp)->l_text)
+
+/*
+ * All repeated structures are kept as linked lists of structures.
+ * All of these start with a LIST structure (except lines, which
+ * have their own abstraction). This will allow for
+ * later conversion to generic list manipulation routines should
+ * I decide to do that. It does mean that there are four extra
+ * bytes per window. I feel that this is an acceptable price,
+ * considering that there are usually only one or two windows.
+ */
+struct list {
+       union {
+               struct mgwin    *l_wp;
+               struct buffer   *x_bp;  /* l_bp is used by struct line */
+               struct list     *l_nxt;
+       } l_p;
+       char *l_name;
+};
+
+/*
+ * Usual hack - to keep from uglifying the code with lotsa
+ * references through the union, we #define something for it.
+ */
+#define l_next l_p.l_nxt
+
+/*
+ * There is a window structure allocated for
+ * every active display window. The windows are kept in a
+ * big list, in top to bottom screen order, with the listhead at
+ * "wheadp". Each window contains its own values of dot and mark.
+ * The flag field contains some bits that are set by commands
+ * to guide redisplay; although this is a bit of a compromise in
+ * terms of decoupling, the full blown redisplay is just too
+ * expensive to run for every input character.
+ */
+struct mgwin {
+       struct list      w_list;        /* List header                  */
+       struct buffer   *w_bufp;        /* Buffer displayed in window   */
+       struct line     *w_linep;       /* Top line in the window       */
+       struct line     *w_dotp;        /* Line containing "."          */
+       struct line     *w_markp;       /* Line containing "mark"       */
+       int              w_doto;        /* Byte offset for "."          */
+       int              w_marko;       /* Byte offset for "mark"       */
+       int              w_toprow;      /* Origin 0 top row of window   */
+       int              w_ntrows;      /* # of rows of text in window  */
+       int              w_frame;       /* #lines to reframe by.        */
+       char             w_rflag;       /* Redisplay Flags.             */
+       char             w_flag;        /* Flags.                       */
+       struct line     *w_wrapline;
+       int              w_dotline;     /* current line number of dot   */
+       int              w_markline;    /* current line number of mark  */
+};
+#define w_wndp w_list.l_p.l_wp
+#define w_name w_list.l_name
+
+/*
+ * Window redisplay flags are set by command processors to
+ * tell the display system what has happened to the buffer
+ * mapped by the window. Setting "WFFULL" is always a safe thing
+ * to do, but it may do more work than is necessary. Always try
+ * to set the simplest action that achieves the required update.
+ * Because commands set bits in the "w_flag", update will see
+ * all change flags, and do the most general one.
+ */
+#define WFFRAME 0x01                   /* Force reframe.                */
+#define WFMOVE 0x02                    /* Movement from line to line.   */
+#define WFEDIT 0x04                    /* Editing within a line.        */
+#define WFFULL 0x08                    /* Do a full display.            */
+#define WFMODE 0x10                    /* Update mode line.             */
+
+/*
+ * Window flags
+ */
+#define WNONE  0x00                    /* No special window options.   */
+#define WEPHEM 0x01                    /* Window is ephemeral.         */
+
+struct undo_rec;
+TAILQ_HEAD(undoq, undo_rec);
+
+/*
+ * Text is kept in buffers. A buffer header, described
+ * below, exists for every buffer in the system. The buffers are
+ * kept in a big list, so that commands that search for a buffer by
+ * name can find the buffer header. There is a safe store for the
+ * dot and mark in the header, but this is only valid if the buffer
+ * is not being displayed (that is, if "b_nwnd" is 0). The text for
+ * the buffer is kept in a circularly linked list of lines, with
+ * a pointer to the header line in "b_headp".
+ */
+struct buffer {
+       struct list      b_list;        /* buffer list pointer           */
+       struct buffer   *b_altb;        /* Link to alternate buffer      */
+       struct line     *b_dotp;        /* Link to "." line structure    */
+       struct line     *b_markp;       /* ditto for mark                */
+       struct line     *b_headp;       /* Link to the header line       */
+       struct maps_s   *b_modes[PBMODES]; /* buffer modes               */
+       int              b_doto;        /* Offset of "." in above line   */
+       int              b_marko;       /* ditto for the "mark"          */
+       short            b_nmodes;      /* number of non-fundamental modes */
+       char             b_nwnd;        /* Count of windows on buffer    */
+       char             b_flag;        /* Flags                         */
+       char             b_fname[NFILEN]; /* File name                   */
+       char             b_cwd[NFILEN]; /* working directory             */
+       struct fileinfo  b_fi;          /* File attributes               */
+       struct undoq     b_undo;        /* Undo actions list             */
+       int              b_undopos;     /* Where we were during last undo */
+       struct undo_rec *b_undoptr;
+       int              b_dotline;     /* Line number of dot */
+       int              b_markline;    /* Line number of mark */
+       int              b_lines;       /* Number of lines in file      */
+};
+#define b_bufp b_list.l_p.x_bp
+#define b_bname b_list.l_name
+
+/* Some helper macros, in case they ever change to functions */
+#define bfirstlp(buf)  (lforw((buf)->b_headp))
+#define blastlp(buf)   (lback((buf)->b_headp))
+
+#define BFCHG  0x01                    /* Changed.                      */
+#define BFBAK  0x02                    /* Need to make a backup.        */
+#ifdef NOTAB
+#define BFNOTAB 0x04                   /* no tab mode                   */
+#endif
+#define BFOVERWRITE 0x08               /* overwrite mode                */
+#define BFREADONLY  0x10               /* read only mode                */
+#define BFDIRTY     0x20               /* Buffer was modified elsewhere */
+#define BFIGNDIRTY  0x40               /* Ignore modifications */
+/*
+ * This structure holds information about recent actions for the Undo command.
+ */
+struct undo_rec {
+       TAILQ_ENTRY(undo_rec) next;
+       enum {
+               INSERT = 1,
+               DELETE,
+               BOUNDARY,
+               MODIFIED,
+               DELREG
+       } type;
+       struct region    region;
+       int              pos;
+       char            *content;
+};
+
+/*
+ * Prototypes.
+ */
+
+/* tty.c X */
+void            ttinit(void);
+void            ttreinit(void);
+void            tttidy(void);
+void            ttmove(int, int);
+void            tteeol(void);
+void            tteeop(void);
+void            ttbeep(void);
+void            ttinsl(int, int, int);
+void            ttdell(int, int, int);
+void            ttwindow(int, int);
+void            ttnowindow(void);
+void            ttcolor(int);
+void            ttresize(void);
+
+volatile sig_atomic_t winch_flag;
+
+/* ttyio.c */
+void            ttopen(void);
+int             ttraw(void);
+void            ttclose(void);
+int             ttcooked(void);
+int             ttputc(int);
+void            ttflush(void);
+int             ttgetc(void);
+int             ttwait(int);
+int             charswaiting(void);
+
+/* dir.c */
+void            dirinit(void);
+int             changedir(int, int);
+int             showcwdir(int, int);
+int             getcwdir(char *, size_t);
+
+/* dired.c */
+struct buffer  *dired_(char *);
+
+/* file.c X */
+int             fileinsert(int, int);
+int             filevisit(int, int);
+int             filevisitalt(int, int);
+int             filevisitro(int, int);
+int             poptofile(int, int);
+struct buffer  *findbuffer(char *);
+int             readin(char *);
+int             insertfile(char *, char *, int);
+int             filewrite(int, int);
+int             filesave(int, int);
+int             buffsave(struct buffer *);
+int             makebkfile(int, int);
+int             writeout(FILE **, struct buffer *, char *);
+void            upmodes(struct buffer *);
+size_t          xbasename(char *, const char *, size_t);
+
+/* line.c X */
+struct line    *lalloc(int);
+int             lrealloc(struct line *, int);
+void            lfree(struct line *);
+void            lchange(int);
+int             linsert_str(const char *, int);
+int             linsert(int, int);
+int             lnewline_at(struct line *, int);
+int             lnewline(void);
+int             ldelete(RSIZE, int);
+int             ldelnewline(void);
+int             lreplace(RSIZE, char *);
+char *          linetostr(const struct line *);
+
+/* yank.c X */
+
+void            kdelete(void);
+int             kinsert(int, int);
+int             kremove(int);
+int             kchunk(char *, RSIZE, int);
+int             killline(int, int);
+int             yank(int, int);
+
+/* window.c X */
+struct mgwin   *new_window(struct buffer *);
+void            free_window(struct mgwin *);
+int             reposition(int, int);
+int             redraw(int, int);
+int             do_redraw(int, int, int);
+int             nextwind(int, int);
+int             prevwind(int, int);
+int             onlywind(int, int);
+int             splitwind(int, int);
+int             enlargewind(int, int);
+int             shrinkwind(int, int);
+int             delwind(int, int);
+
+/* buffer.c */
+int             togglereadonly(int, int);
+struct buffer   *bfind(const char *, int);
+int             poptobuffer(int, int);
+int             killbuffer(struct buffer *);
+int             killbuffer_cmd(int, int);
+int             savebuffers(int, int);
+int             listbuffers(int, int);
+int             addlinef(struct buffer *, char *, ...);
+#define         addline(bp, text)      addlinef(bp, "%s", text)
+int             anycb(int);
+int             bclear(struct buffer *);
+int             showbuffer(struct buffer *, struct mgwin *, int);
+int             augbname(char *, const char *, size_t);
+struct mgwin    *popbuf(struct buffer *, int);
+int             bufferinsert(int, int);
+int             usebuffer(int, int);
+int             notmodified(int, int);
+int             popbuftop(struct buffer *, int);
+int             getbufcwd(char *, size_t);
+int             checkdirty(struct buffer *);
+
+/* display.c */
+int            vtresize(int, int, int);
+void           vtinit(void);
+void           vttidy(void);
+void           update(void);
+int            linenotoggle(int, int);
+
+/* echo.c X */
+void            eerase(void);
+int             eyorn(const char *);
+int             eyesno(const char *);
+void            ewprintf(const char *fmt, ...);
+char           *ereply(const char *, char *, size_t, ...);
+char           *eread(const char *, char *, size_t, int, ...);
+int             getxtra(struct list *, struct list *, int, int);
+void            free_file_list(struct list *);
+
+/* fileio.c */
+int             ffropen(FILE **, const char *, struct buffer *);
+void            ffstat(FILE *, struct buffer *);
+int             ffwopen(FILE **, const char *, struct buffer *);
+int             ffclose(FILE *, struct buffer *);
+int             ffputbuf(FILE *, struct buffer *);
+int             ffgetline(FILE *, char *, int, int *);
+int             fbackupfile(const char *);
+char           *adjustname(const char *, int);
+char           *startupfile(char *);
+int             copy(char *, char *);
+struct list    *make_file_list(char *);
+int             fisdir(const char *);
+int             fchecktime(struct buffer *);
+int             fupdstat(struct buffer *);
+int             backuptohomedir(int, int);
+int             toggleleavetmp(int, int);
+
+/* kbd.c X */
+int             do_meta(int, int);
+int             bsmap(int, int);
+void            ungetkey(int);
+int             getkey(int);
+int             doin(void);
+int             rescan(int, int);
+int             universal_argument(int, int);
+int             digit_argument(int, int);
+int             negative_argument(int, int);
+int             selfinsert(int, int);
+int             quote(int, int);
+
+/* main.c */
+int             ctrlg(int, int);
+int             quit(int, int);
+
+/* ttyio.c */
+void            panic(char *);
+
+/* cinfo.c */
+char           *getkeyname(char  *, size_t, int);
+
+/* basic.c */
+int             gotobol(int, int);
+int             backchar(int, int);
+int             gotoeol(int, int);
+int             forwchar(int, int);
+int             gotobob(int, int);
+int             gotoeob(int, int);
+int             forwline(int, int);
+int             backline(int, int);
+void            setgoal(void);
+int             getgoal(struct line *);
+int             forwpage(int, int);
+int             backpage(int, int);
+int             forw1page(int, int);
+int             back1page(int, int);
+int             pagenext(int, int);
+void            isetmark(void);
+int             setmark(int, int);
+int             clearmark(int, int);
+int             swapmark(int, int);
+int             gotoline(int, int);
+
+/* random.c X */
+int             showcpos(int, int);
+int             getcolpos(void);
+int             twiddle(int, int);
+int             openline(int, int);
+int             newline(int, int);
+int             deblank(int, int);
+int             justone(int, int);
+int             delwhite(int, int);
+int             delleadwhite(int, int);
+int             deltrailwhite(int, int);
+int             lfindent(int, int);
+int             indent(int, int);
+int             forwdel(int, int);
+int             backdel(int, int);
+int             space_to_tabstop(int, int);
+int             backtoindent(int, int);
+int             joinline(int, int);
+
+/* tags.c X */
+int             findtag(int, int);
+int             poptag(int, int);
+int             tagsvisit(int, int);
+int             curtoken(int, int, char *);
+
+/* cscope.c */
+int            cssymbol(int, int);
+int            csdefinition(int, int);
+int            csfuncalled(int, int);
+int            cscallerfuncs(int, int);
+int            csfindtext(int, int);
+int            csegrep(int, int);
+int            csfindfile(int, int);
+int            csfindinc(int, int);
+int            csnextfile(int, int);
+int            csnextmatch(int, int);
+int            csprevfile(int, int);
+int            csprevmatch(int, int);
+int            cscreatelist(int, int);
+
+/* extend.c X */
+int             insert(int, int);
+int             bindtokey(int, int);
+int             localbind(int, int);
+int             redefine_key(int, int);
+int             unbindtokey(int, int);
+int             localunbind(int, int);
+int             extend(int, int);
+int             evalexpr(int, int);
+int             evalbuffer(int, int);
+int             evalfile(int, int);
+int             load(const char *);
+int             excline(char *);
+
+/* help.c X */
+int             desckey(int, int);
+int             wallchart(int, int);
+int             help_help(int, int);
+int             apropos_command(int, int);
+
+/* paragraph.c X */
+int             gotobop(int, int);
+int             gotoeop(int, int);
+int             fillpara(int, int);
+int             killpara(int, int);
+int             fillword(int, int);
+int             setfillcol(int, int);
+
+/* word.c X */
+int             backword(int, int);
+int             forwword(int, int);
+int             upperword(int, int);
+int             lowerword(int, int);
+int             capword(int, int);
+int             delfword(int, int);
+int             delbword(int, int);
+int             inword(void);
+
+/* region.c X */
+int             killregion(int, int);
+int             copyregion(int, int);
+int             lowerregion(int, int);
+int             upperregion(int, int);
+int             prefixregion(int, int);
+int             setprefix(int, int);
+int             region_get_data(struct region *, char *, int);
+void            region_put_data(const char *, int);
+int             markbuffer(int, int);
+int             piperegion(int, int);
+
+/* search.c X */
+int             forwsearch(int, int);
+int             backsearch(int, int);
+int             searchagain(int, int);
+int             forwisearch(int, int);
+int             backisearch(int, int);
+int             queryrepl(int, int);
+int             forwsrch(void);
+int             backsrch(void);
+int             readpattern(char *);
+
+/* spawn.c X */
+int             spawncli(int, int);
+
+/* ttykbd.c X */
+void            ttykeymapinit(void);
+void            ttykeymaptidy(void);
+
+/* match.c X */
+int             showmatch(int, int);
+
+/* version.c X */
+int             showversion(int, int);
+
+/* macro.c X */
+int             definemacro(int, int);
+int             finishmacro(int, int);
+int             executemacro(int, int);
+
+/* modes.c X */
+int             indentmode(int, int);
+int             fillmode(int, int);
+int             blinkparen(int, int);
+#ifdef NOTAB
+int             notabmode(int, int);
+#endif /* NOTAB */
+int             overwrite_mode(int, int);
+int             set_default_mode(int,int);
+
+#ifdef REGEX
+/* re_search.c X */
+int             re_forwsearch(int, int);
+int             re_backsearch(int, int);
+int             re_searchagain(int, int);
+int             re_queryrepl(int, int);
+int             replstr(int, int);
+int             setcasefold(int, int);
+int             delmatchlines(int, int);
+int             delnonmatchlines(int, int);
+int             cntmatchlines(int, int);
+int             cntnonmatchlines(int, int);
+#endif /* REGEX */
+
+/* undo.c X */
+void            free_undo_record(struct undo_rec *);
+int             undo_dump(int, int);
+int             undo_enabled(void);
+int             undo_enable(int, int);
+int             undo_add_boundary(int, int);
+void            undo_add_modified(void);
+int             undo_add_insert(struct line *, int, int);
+int             undo_add_delete(struct line *, int, int, int);
+int             undo_boundary_enable(int, int);
+int             undo_add_change(struct line *, int, int);
+int             undo(int, int);
+
+/* autoexec.c X */
+int             auto_execute(int, int);
+PF             *find_autoexec(const char *);
+int             add_autoexec(const char *, const char *);
+
+/* cmode.c X */
+int             cmode(int, int);
+int             cc_brace(int, int);
+int             cc_char(int, int);
+int             cc_tab(int, int);
+int             cc_indent(int, int);
+int             cc_lfindent(int, int);
+
+/* grep.c X */
+int             next_error(int, int);
+int             globalwdtoggle(int, int);
+int             compile(int, int);
+
+/*
+ * Externals.
+ */
+extern struct buffer   *bheadp;
+extern struct buffer   *curbp;
+extern struct mgwin    *curwp;
+extern struct mgwin    *wheadp;
+extern int              thisflag;
+extern int              lastflag;
+extern int              curgoal;
+extern int              startrow;
+extern int              epresf;
+extern int              sgarbf;
+extern int              mode;
+extern int              nrow;
+extern int              ncol;
+extern int              ttrow;
+extern int              ttcol;
+extern int              tttop;
+extern int              ttbot;
+extern int              tthue;
+extern int              defb_nmodes;
+extern int              defb_flag;
+extern char             cinfo[];
+extern char            *keystrings[];
+extern char             pat[NPAT];
+#ifndef NO_DPROMPT
+extern char             prompt[];
+#endif /* !NO_DPROMPT */
+
+/*
+ * Globals.
+ */
+int             tceeol;
+int             tcinsl;
+int             tcdell;
+int             rptcount;      /* successive invocation count */
diff --git a/mg/dir.c b/mg/dir.c
new file mode 100644 (file)
index 0000000..2352773
--- /dev/null
+++ b/mg/dir.c
@@ -0,0 +1,77 @@
+/*     $OpenBSD: dir.c,v 1.19 2008/06/13 20:07:40 kjell Exp $  */
+
+/* This file is in the public domain. */
+
+/*
+ * Name:       MG 2a
+ *             Directory management functions
+ * Created:    Ron Flax (ron@vsedev.vse.com)
+ *             Modified for MG 2a by Mic Kaczmarczik 03-Aug-1987
+ */
+
+#include "def.h"
+
+static char     mgcwd[NFILEN];
+
+/*
+ * Initialize anything the directory management routines need.
+ */
+void
+dirinit(void)
+{
+       mgcwd[0] = '\0';
+       if (getcwd(mgcwd, sizeof(mgcwd)) == NULL) {
+               ewprintf("Can't get current directory!");
+               chdir("/");
+       }
+       if (!(mgcwd[0] == '/' && mgcwd [1] == '\0'))
+               (void)strlcat(mgcwd, "/", sizeof(mgcwd));
+}
+
+/*
+ * Change current working directory.
+ */
+/* ARGSUSED */
+int
+changedir(int f, int n)
+{
+       char    bufc[NFILEN], *bufp;
+
+       (void)strlcpy(bufc, mgcwd, sizeof(bufc));
+       if ((bufp = eread("Change default directory: ", bufc, NFILEN,
+           EFDEF | EFNEW | EFCR | EFFILE)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       /* Append trailing slash */
+       if (chdir(bufc) == -1) {
+               ewprintf("Can't change dir to %s", bufc);
+               return (FALSE);
+       }
+       if ((bufp = getcwd(mgcwd, sizeof(mgcwd))) == NULL)
+               panic("Can't get current directory!");
+       if (mgcwd[strlen(mgcwd) - 1] != '/')
+               (void)strlcat(mgcwd, "/", sizeof(mgcwd));
+       ewprintf("Current directory is now %s", bufp);
+       return (TRUE);
+}
+
+/*
+ * Show current directory.
+ */
+/* ARGSUSED */
+int
+showcwdir(int f, int n)
+{
+       ewprintf("Current directory: %s", mgcwd);
+       return (TRUE);
+}
+
+int
+getcwdir(char *buf, size_t len)
+{
+       if (strlcpy(buf, mgcwd, len) >= len)
+               return (FALSE);
+
+       return (TRUE);
+}
diff --git a/mg/dired.c b/mg/dired.c
new file mode 100644 (file)
index 0000000..184c9f7
--- /dev/null
@@ -0,0 +1,779 @@
+/*     $OpenBSD: dired.c,v 1.51 2012/03/14 13:56:35 lum Exp $  */
+
+/* This file is in the public domain. */
+
+/* dired module for mg 2a
+ * by Robert A. Larson
+ */
+
+#include "def.h"
+#include "funmap.h"
+#include "kbd.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdarg.h>
+
+void            dired_init(void);
+static int      dired(int, int);
+static int      d_otherwindow(int, int);
+static int      d_undel(int, int);
+static int      d_undelbak(int, int);
+static int      d_findfile(int, int);
+static int      d_ffotherwindow(int, int);
+static int      d_expunge(int, int);
+static int      d_copy(int, int);
+static int      d_del(int, int);
+static int      d_rename(int, int);
+static int      d_exec(int, struct buffer *, const char *, const char *, ...);
+static int      d_shell_command(int, int);
+static int      d_create_directory(int, int);
+static int      d_makename(struct line *, char *, size_t);
+static int      d_warpdot(struct line *, int *);
+static int      d_forwpage(int, int);
+static int      d_backpage(int, int);
+static int      d_forwline(int, int);
+static int      d_backline(int, int);
+static void     reaper(int);
+
+extern struct keymap_s helpmap, cXmap, metamap;
+
+static PF dirednul[] = {
+       setmark,                /* ^@ */
+       gotobol,                /* ^A */
+       backchar,               /* ^B */
+       rescan,                 /* ^C */
+       d_del,                  /* ^D */
+       gotoeol,                /* ^E */
+       forwchar,               /* ^F */
+       ctrlg,                  /* ^G */
+       NULL,                   /* ^H */
+};
+
+static PF diredcl[] = {
+       reposition,             /* ^L */
+       d_findfile,             /* ^M */
+       d_forwline,             /* ^N */
+       rescan,                 /* ^O */
+       d_backline,             /* ^P */
+       rescan,                 /* ^Q */
+       backisearch,            /* ^R */
+       forwisearch,            /* ^S */
+       rescan,                 /* ^T */
+       universal_argument,     /* ^U */
+       d_forwpage,             /* ^V */
+       rescan,                 /* ^W */
+       NULL                    /* ^X */
+};
+
+static PF diredcz[] = {
+       spawncli,               /* ^Z */
+       NULL,                   /* esc */
+       rescan,                 /* ^\ */
+       rescan,                 /* ^] */
+       rescan,                 /* ^^ */
+       rescan,                 /* ^_ */
+       d_forwline,             /* SP */
+       d_shell_command,        /* ! */
+       rescan,                 /* " */
+       rescan,                 /* # */
+       rescan,                 /* $ */
+       rescan,                 /* % */
+       rescan,                 /* & */
+       rescan,                 /* ' */
+       rescan,                 /* ( */
+       rescan,                 /* ) */
+       rescan,                 /* * */
+       d_create_directory      /* + */
+};
+
+static PF diredc[] = {
+       d_copy,                 /* c */
+       d_del,                  /* d */
+       d_findfile,             /* e */
+       d_findfile              /* f */
+};
+
+static PF diredn[] = {
+       d_forwline,             /* n */
+       d_ffotherwindow,        /* o */
+       d_backline,             /* p */
+       rescan,                 /* q */
+       d_rename,               /* r */
+       rescan,                 /* s */
+       rescan,                 /* t */
+       d_undel,                /* u */
+       rescan,                 /* v */
+       rescan,                 /* w */
+       d_expunge               /* x */
+};
+
+static PF direddl[] = {
+       d_undelbak              /* del */
+};
+
+static PF diredbp[] = {
+       d_backpage              /* v */ 
+};
+
+static PF dirednull[] = {
+       NULL
+};
+
+#ifndef        DIRED_XMAPS
+#define        NDIRED_XMAPS    0       /* number of extra map sections */
+#endif /* DIRED_XMAPS */
+
+static struct KEYMAPE (1 + IMAPEXT) d_backpagemap = {
+       1,
+       1 + IMAPEXT,
+       rescan,                 
+       {
+               {
+               'v', 'v', diredbp, NULL
+               }
+       }
+};
+
+static struct KEYMAPE (7 + NDIRED_XMAPS + IMAPEXT) diredmap = {
+       7 + NDIRED_XMAPS,
+       7 + NDIRED_XMAPS + IMAPEXT,
+       rescan,
+       {
+               {
+                       CCHR('@'), CCHR('H'), dirednul, (KEYMAP *) & helpmap
+               },
+               {
+                       CCHR('L'), CCHR('X'), diredcl, (KEYMAP *) & cXmap
+               },
+               {
+                       CCHR('['), CCHR('['), dirednull, (KEYMAP *) & 
+                       d_backpagemap
+               },
+               {
+                       CCHR('Z'), '+', diredcz, (KEYMAP *) & metamap
+               },
+               {
+                       'c', 'f', diredc, NULL
+               },
+               {
+                       'n', 'x', diredn, NULL
+               },
+               {
+                       CCHR('?'), CCHR('?'), direddl, NULL
+               },
+#ifdef DIRED_XMAPS
+               DIRED_XMAPS,    /* map sections for dired mode keys      */
+#endif /* DIRED_XMAPS */
+       }
+};
+
+void
+dired_init(void)
+{
+       funmap_add(dired, "dired");
+       funmap_add(d_undelbak, "dired-backup-unflag");
+       funmap_add(d_copy, "dired-copy-file");
+       funmap_add(d_expunge, "dired-do-deletions");
+       funmap_add(d_findfile, "dired-find-file");
+       funmap_add(d_ffotherwindow, "dired-find-file-other-window");
+       funmap_add(d_del, "dired-flag-file-deleted");
+       funmap_add(d_forwline, "dired-next-line");
+       funmap_add(d_otherwindow, "dired-other-window");
+       funmap_add(d_backline, "dired-previous-line");
+       funmap_add(d_rename, "dired-rename-file");
+       funmap_add(d_backpage, "dired-scroll-down");
+       funmap_add(d_forwpage, "dired-scroll-up");
+       funmap_add(d_undel, "dired-unflag");
+       maps_add((KEYMAP *)&diredmap, "dired");
+       dobindkey(fundamental_map, "dired", "^Xd");
+}
+
+/* ARGSUSED */
+int
+dired(int f, int n)
+{
+       char             dname[NFILEN], *bufp, *slash;
+       struct buffer   *bp;
+
+       if (curbp->b_fname && curbp->b_fname[0] != '\0') {
+               (void)strlcpy(dname, curbp->b_fname, sizeof(dname));
+               if ((slash = strrchr(dname, '/')) != NULL) {
+                       *(slash + 1) = '\0';
+               }
+       } else {
+               if (getcwd(dname, sizeof(dname)) == NULL)
+                       dname[0] = '\0';
+       }
+
+       if ((bufp = eread("Dired: ", dname, NFILEN,
+           EFDEF | EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       if (bufp[0] == '\0')
+               return (FALSE);
+       if ((bp = dired_(bufp)) == NULL)
+               return (FALSE);
+
+       curbp = bp;
+       return (showbuffer(bp, curwp, WFFULL | WFMODE));
+}
+
+/* ARGSUSED */
+int
+d_otherwindow(int f, int n)
+{
+       char             dname[NFILEN], *bufp, *slash;
+       struct buffer   *bp;
+       struct mgwin    *wp;
+
+       if (curbp->b_fname && curbp->b_fname[0] != '\0') {
+               (void)strlcpy(dname, curbp->b_fname, sizeof(dname));
+               if ((slash = strrchr(dname, '/')) != NULL) {
+                       *(slash + 1) = '\0';
+               }
+       } else {
+               if (getcwd(dname, sizeof(dname)) == NULL)
+                       dname[0] = '\0';
+       }
+
+       if ((bufp = eread("Dired other window: ", dname, NFILEN,
+           EFDEF | EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       if ((bp = dired_(bufp)) == NULL)
+               return (FALSE);
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       curwp = wp;
+       return (TRUE);
+}
+
+/* ARGSUSED */
+int
+d_del(int f, int n)
+{
+       if (n < 0)
+               return (FALSE);
+       while (n--) {
+               if (llength(curwp->w_dotp) > 0)
+                       lputc(curwp->w_dotp, 0, 'D');
+               if (lforw(curwp->w_dotp) != curbp->b_headp)
+                       curwp->w_dotp = lforw(curwp->w_dotp);
+       }
+       curwp->w_rflag |= WFEDIT | WFMOVE;
+       curwp->w_doto = 0;
+       return (TRUE);
+}
+
+/* ARGSUSED */
+int
+d_undel(int f, int n)
+{
+       if (n < 0)
+               return (d_undelbak(f, -n));
+       while (n--) {
+               if (llength(curwp->w_dotp) > 0)
+                       lputc(curwp->w_dotp, 0, ' ');
+               if (lforw(curwp->w_dotp) != curbp->b_headp)
+                       curwp->w_dotp = lforw(curwp->w_dotp);
+       }
+       curwp->w_rflag |= WFEDIT | WFMOVE;
+       curwp->w_doto = 0;
+       return (TRUE);
+}
+
+/* ARGSUSED */
+int
+d_undelbak(int f, int n)
+{
+       if (n < 0)
+               return (d_undel(f, -n));
+       while (n--) {
+               if (llength(curwp->w_dotp) > 0)
+                       lputc(curwp->w_dotp, 0, ' ');
+               if (lback(curwp->w_dotp) != curbp->b_headp)
+                       curwp->w_dotp = lback(curwp->w_dotp);
+       }
+       curwp->w_doto = 0;
+       curwp->w_rflag |= WFEDIT | WFMOVE;
+       return (TRUE);
+}
+
+/* ARGSUSED */
+int
+d_findfile(int f, int n)
+{
+       struct buffer   *bp;
+       int              s;
+       char             fname[NFILEN];
+
+       if ((s = d_makename(curwp->w_dotp, fname, sizeof(fname))) == ABORT)
+               return (FALSE);
+       if (s == TRUE)
+               bp = dired_(fname);
+       else
+               bp = findbuffer(fname);
+       if (bp == NULL)
+               return (FALSE);
+       curbp = bp;
+       if (showbuffer(bp, curwp, WFFULL) != TRUE)
+               return (FALSE);
+       if (bp->b_fname[0] != 0)
+               return (TRUE);
+       return (readin(fname));
+}
+
+/* ARGSUSED */
+int
+d_ffotherwindow(int f, int n)
+{
+       char             fname[NFILEN];
+       int              s;
+       struct buffer   *bp;
+       struct mgwin    *wp;
+
+       if ((s = d_makename(curwp->w_dotp, fname, sizeof(fname))) == ABORT)
+               return (FALSE);
+       if ((bp = (s ? dired_(fname) : findbuffer(fname))) == NULL)
+               return (FALSE);
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       curwp = wp;
+       if (bp->b_fname[0] != 0)
+               return (TRUE);  /* never true for dired buffers */
+       return (readin(fname));
+}
+
+/* ARGSUSED */
+int
+d_expunge(int f, int n)
+{
+       struct line     *lp, *nlp;
+       char             fname[NFILEN], sname[NFILEN];
+
+       for (lp = bfirstlp(curbp); lp != curbp->b_headp; lp = nlp) {
+               nlp = lforw(lp);
+               if (llength(lp) && lgetc(lp, 0) == 'D') {
+                       switch (d_makename(lp, fname, sizeof(fname))) {
+                       case ABORT:
+                               ewprintf("Bad line in dired buffer");
+                               return (FALSE);
+                       case FALSE:
+                               if (unlink(fname) < 0) {
+                                       (void)xbasename(sname, fname, NFILEN);
+                                       ewprintf("Could not delete '%s'", sname);
+                                       return (FALSE);
+                               }
+                               break;
+                       case TRUE:
+                               if (rmdir(fname) < 0) {
+                                       (void)xbasename(sname, fname, NFILEN);
+                                       ewprintf("Could not delete directory "
+                                           "'%s'", sname);
+                                       return (FALSE);
+                               }
+                               break;
+                       }
+                       lfree(lp);
+                       curwp->w_bufp->b_lines--;
+                       curwp->w_rflag |= WFFULL;
+               }
+       }
+       return (TRUE);
+}
+
+/* ARGSUSED */
+int
+d_copy(int f, int n)
+{
+       char    frname[NFILEN], toname[NFILEN], sname[NFILEN], *bufp;
+       int     ret;
+       size_t  off;
+       struct buffer *bp;
+
+       if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) {
+               ewprintf("Not a file");
+               return (FALSE);
+       }
+       off = strlcpy(toname, curbp->b_fname, sizeof(toname));
+       if (off >= sizeof(toname) - 1) {        /* can't happen, really */
+               ewprintf("Directory name too long");
+               return (FALSE);
+       }
+       (void)xbasename(sname, frname, NFILEN);
+       bufp = eread("Copy %s to: ", toname, sizeof(toname),
+           EFDEF | EFNEW | EFCR, sname);
+       if (bufp == NULL) 
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       ret = (copy(frname, toname) >= 0) ? TRUE : FALSE;
+       if (ret != TRUE)
+               return (ret);
+       bp = dired_(curbp->b_fname);
+       return (showbuffer(bp, curwp, WFFULL | WFMODE));
+}
+
+/* ARGSUSED */
+int
+d_rename(int f, int n)
+{
+       char             frname[NFILEN], toname[NFILEN], *bufp;
+       int              ret;
+       size_t           off;
+       struct buffer   *bp;
+       char             sname[NFILEN];
+
+       if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) {
+               ewprintf("Not a file");
+               return (FALSE);
+       }
+       off = strlcpy(toname, curbp->b_fname, sizeof(toname));
+       if (off >= sizeof(toname) - 1) {        /* can't happen, really */
+               ewprintf("Directory name too long");
+               return (FALSE);
+       }
+       (void)xbasename(sname, frname, NFILEN);
+       bufp = eread("Rename %s to: ", toname,
+           sizeof(toname), EFDEF | EFNEW | EFCR, sname);
+       if (bufp == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       ret = (rename(frname, toname) >= 0) ? TRUE : FALSE;
+       if (ret != TRUE)
+               return (ret);
+       bp = dired_(curbp->b_fname);
+       return (showbuffer(bp, curwp, WFFULL | WFMODE));
+}
+
+/* ARGSUSED */
+void
+reaper(int signo __attribute__((unused)))
+{
+       int     save_errno = errno, status;
+
+       while (waitpid(-1, &status, WNOHANG) >= 0)
+               ;
+       errno = save_errno;
+}
+
+/*
+ * Pipe the currently selected file through a shell command.
+ */
+/* ARGSUSED */
+int
+d_shell_command(int f, int n)
+{
+       char             command[512], fname[MAXPATHLEN], *bufp;
+       struct buffer   *bp;
+       struct mgwin    *wp;
+       char             sname[NFILEN];
+
+       bp = bfind("*Shell Command Output*", TRUE);
+       if (bclear(bp) != TRUE)
+               return (ABORT);
+
+       if (d_makename(curwp->w_dotp, fname, sizeof(fname)) != FALSE) {
+               ewprintf("bad line");
+               return (ABORT);
+       }
+
+       command[0] = '\0';
+       (void)xbasename(sname, fname, NFILEN);
+       bufp = eread("! on %s: ", command, sizeof(command), EFNEW, sname);
+       if (bufp == NULL)
+               return (ABORT);
+
+       if (d_exec(0, bp, fname, "sh", "-c", command, NULL) != TRUE)
+               return (ABORT);
+
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (ABORT); /* XXX - free the buffer?? */
+       curwp = wp;
+       curbp = wp->w_bufp;
+       return (TRUE);
+}
+
+/*
+ * Pipe input file to cmd and insert the command's output in the
+ * given buffer.  Each line will be prefixed with the given
+ * number of spaces.
+ */
+static int
+d_exec(int space, struct buffer *bp, const char *input, const char *cmd, ...)
+{
+       char     buf[BUFSIZ];
+       va_list  ap;
+       struct   sigaction olda, newa;
+       char    **argv = NULL, *cp;
+       FILE    *fin;
+       int      fds[2] = { -1, -1 };
+       int      infd = -1;
+       int      ret = (ABORT), n;
+       pid_t    pid;
+
+       if (sigaction(SIGCHLD, NULL, &olda) == -1)
+               return (ABORT);
+
+       /* Find the number of arguments. */
+       va_start(ap, cmd);
+       for (n = 2; va_arg(ap, char *) != NULL; n++)
+               ;
+       va_end(ap);
+
+       /* Allocate and build the argv. */
+       if ((argv = calloc(n, sizeof(*argv))) == NULL) {
+               ewprintf("Can't allocate argv : %s", strerror(errno));
+               goto out;
+       }
+
+       n = 1;
+       argv[0] = (char *)cmd;
+       va_start(ap, cmd);
+       while ((argv[n] = va_arg(ap, char *)) != NULL)
+               n++;
+       va_end(ap);
+
+       if (input == NULL)
+               input = "/dev/null";
+
+       if ((infd = open(input, O_RDONLY)) == -1) {
+               ewprintf("Can't open input file : %s", strerror(errno));
+               goto out;
+       }
+
+       if (pipe(fds) == -1) {
+               ewprintf("Can't create pipe : %s", strerror(errno));
+               goto out;
+       }
+
+       newa.sa_handler = reaper;
+       newa.sa_flags = 0;
+       if (sigaction(SIGCHLD, &newa, NULL) == -1)
+               goto out;
+
+       if ((pid = fork()) == -1) {
+               ewprintf("Can't fork");
+               goto out;
+       }
+
+       switch (pid) {
+       case 0: /* Child */
+               close(fds[0]);
+               dup2(infd, STDIN_FILENO);
+               dup2(fds[1], STDOUT_FILENO);
+               dup2(fds[1], STDERR_FILENO);
+               if (execvp(argv[0], argv) == -1)
+                       ewprintf("Can't exec %s: %s", argv[0], strerror(errno));
+               exit(1);
+               break;
+       default: /* Parent */
+               close(infd);
+               close(fds[1]);
+               infd = fds[1] = -1;
+               if ((fin = fdopen(fds[0], "r")) == NULL)
+                       goto out;
+               while (fgets(buf, sizeof(buf), fin) != NULL) {
+                       cp = strrchr(buf, '\n');
+                       if (cp == NULL && !feof(fin)) { /* too long a line */
+                               int c;
+                               addlinef(bp, "%*s%s...", space, "", buf);
+                               while ((c = getc(fin)) != EOF && c != '\n')
+                                       ;
+                               continue;
+                       } else if (cp)
+                               *cp = '\0';
+                       addlinef(bp, "%*s%s", space, "", buf);
+               }
+               fclose(fin);
+               break;
+       }
+       ret = (TRUE);
+
+out:
+       if (sigaction(SIGCHLD, &olda, NULL) == -1)
+               ewprintf("Warning, couldn't reset previous signal handler");
+       if (fds[0] != -1)
+               close(fds[0]);
+       if (fds[1] != -1)
+               close(fds[1]);
+       if (infd != -1)
+               close(infd);
+       if (argv != NULL)
+               free(argv);
+       return ret;
+}
+
+/* ARGSUSED */
+int
+d_create_directory(int f, int n)
+{
+       char     tocreate[MAXPATHLEN], *bufp;
+       size_t  off;
+       struct buffer   *bp;
+
+       off = strlcpy(tocreate, curbp->b_fname, sizeof(tocreate));
+       if (off >= sizeof(tocreate) - 1)
+               return (FALSE);
+       if ((bufp = eread("Create directory: ", tocreate,
+           sizeof(tocreate), EFDEF | EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       if (mkdir(tocreate, 0755) == -1) {
+               ewprintf("Creating directory: %s, %s", strerror(errno),
+                   tocreate);
+               return (FALSE);
+       }
+       bp = dired_(curbp->b_fname);
+       return (showbuffer(bp, curwp, WFFULL | WFMODE));
+}
+
+static int
+d_makename(struct line *lp, char *fn, size_t len)
+{
+       int      start, nlen;
+       char    *namep;
+
+       if (d_warpdot(lp, &start) == FALSE)
+               return (ABORT);
+       namep = &lp->l_text[start];
+       nlen = llength(lp) - start;
+
+       if (snprintf(fn, len, "%s%.*s", curbp->b_fname, nlen, namep) >= len)
+               return (ABORT); /* Name is too long. */
+
+       /* Return TRUE if the entry is a directory. */
+       return ((lgetc(lp, 2) == 'd') ? TRUE : FALSE);
+}
+
+#define NAME_FIELD     9
+
+static int
+d_warpdot(struct line *dotp, int *doto)
+{
+       char *tp = dotp->l_text;
+       int off = 0, field = 0, len;
+
+       /*
+        * Find the byte offset to the (space-delimited) filename
+        * field in formatted ls output.
+        */
+       len = llength(dotp);
+       while (off < len) {
+               if (tp[off++] == ' ') {
+                       if (++field == NAME_FIELD) {
+                               *doto = off;
+                               return (TRUE);
+                       }
+                       /* Skip the space. */
+                       while (off < len && tp[off] == ' ')
+                               off++;
+               }
+       }
+       /* We didn't find the field. */
+       *doto = 0;
+       return (FALSE);
+}
+
+static int
+d_forwpage(int f, int n) 
+{
+       forwpage(f | FFRAND, n);
+       return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
+}
+
+static int 
+d_backpage (int f, int n)
+{
+       backpage(f | FFRAND, n);
+       return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
+}
+
+static int
+d_forwline (int f, int n)
+{
+       forwline(f | FFRAND, n);
+       return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
+}
+
+static int
+d_backline (int f, int n)
+{
+       backline(f | FFRAND, n);
+       return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
+}
+
+/*
+ * XXX dname needs to have enough place to store an additional '/'.
+ */
+struct buffer *
+dired_(char *dname)
+{
+       struct buffer   *bp;
+       int              len, i;
+
+       if ((fopen(dname,"r")) == NULL) {
+               if (errno == EACCES)
+                       ewprintf("Permission denied");
+               return (NULL);
+       }
+       if ((dname = adjustname(dname, FALSE)) == NULL) {
+               ewprintf("Bad directory name");
+               return (NULL);
+       }
+       /* this should not be done, instead adjustname() should get a flag */
+       len = strlen(dname);
+       if (dname[len - 1] != '/') {
+               dname[len++] = '/';
+               dname[len] = '\0';
+       }
+       if ((bp = findbuffer(dname)) == NULL) {
+               ewprintf("Could not create buffer");
+               return (NULL);
+       }
+       if (bclear(bp) != TRUE)
+               return (NULL);
+       bp->b_flag |= BFREADONLY;
+
+       if ((d_exec(2, bp, NULL, "ls", "-al", dname, NULL)) != TRUE)
+               return (NULL);
+
+       /* Find the line with ".." on it. */
+       bp->b_dotp = bfirstlp(bp);
+       for (i = 0; i < bp->b_lines; i++) {
+               bp->b_dotp = lforw(bp->b_dotp);
+               if (d_warpdot(bp->b_dotp, &bp->b_doto) == FALSE)
+                       continue;
+               if (strcmp(ltext(bp->b_dotp) + bp->b_doto, "..") == 0)
+                       break;
+       }
+
+       /* We want dot on the entry right after "..", if possible. */
+       if (++i < bp->b_lines - 2)
+               bp->b_dotp = lforw(bp->b_dotp);
+       d_warpdot(bp->b_dotp, &bp->b_doto);
+
+       (void)strlcpy(bp->b_fname, dname, sizeof(bp->b_fname));
+       (void)strlcpy(bp->b_cwd, dname, sizeof(bp->b_cwd));
+       if ((bp->b_modes[1] = name_mode("dired")) == NULL) {
+               bp->b_modes[0] = name_mode("fundamental");
+               ewprintf("Could not find mode dired");
+               return (NULL);
+       }
+       bp->b_nmodes = 1;
+       return (bp);
+}
diff --git a/mg/display.c b/mg/display.c
new file mode 100644 (file)
index 0000000..ba7c580
--- /dev/null
@@ -0,0 +1,1064 @@
+/*     $OpenBSD: display.c,v 1.37 2009/06/04 02:23:37 kjell Exp $      */
+
+/* This file is in the public domain. */
+
+/*
+ * The functions in this file handle redisplay. The
+ * redisplay system knows almost nothing about the editing
+ * process; the editing functions do, however, set some
+ * hints to eliminate a lot of the grinding. There is more
+ * that can be done; the "vtputc" interface is a real
+ * pig.
+ */
+#include "def.h"
+#include "kbd.h"
+
+#include <ctype.h>
+
+/*
+ * You can change these back to the types
+ * implied by the name if you get tight for space. If you
+ * make both of them "int" you get better code on the VAX.
+ * They do nothing if this is not Gosling redisplay, except
+ * for change the size of a structure that isn't used.
+ * A bit of a cheat.
+ */
+#define        XCHAR   int
+#define        XSHORT  int
+
+#ifdef STANDOUT_GLITCH
+#include <term.h>
+#endif
+
+/*
+ * A video structure always holds
+ * an array of characters whose length is equal to
+ * the longest line possible. v_text is allocated
+ * dynamically to fit the screen width.
+ */
+struct video {
+       short   v_hash;         /* Hash code, for compares.      */
+       short   v_flag;         /* Flag word.                    */
+       short   v_color;        /* Color of the line.            */
+       XSHORT  v_cost;         /* Cost of display.              */
+       char    *v_text;        /* The actual characters.        */
+};
+
+#define VFCHG  0x0001                  /* Changed.                      */
+#define VFHBAD 0x0002                  /* Hash and cost are bad.        */
+#define VFEXT  0x0004                  /* extended line (beond ncol)    */
+
+/*
+ * SCORE structures hold the optimal
+ * trace trajectory, and the cost of redisplay, when
+ * the dynamic programming redisplay code is used.
+ * If no fancy redisplay, this isn't used. The trace index
+ * fields can be "char", and the cost a "short", but
+ * this makes the code worse on the VAX.
+ */
+struct score {
+       XCHAR   s_itrace;       /* "i" index for track back.     */
+       XCHAR   s_jtrace;       /* "j" index for trace back.     */
+       XSHORT  s_cost;         /* Display cost.                 */
+};
+
+void   vtmove(int, int);
+void   vtputc(int);
+void   vtpute(int);
+int    vtputs(const char *);
+void   vteeol(void);
+void   updext(int, int);
+void   modeline(struct mgwin *);
+void   setscores(int, int);
+void   traceback(int, int, int, int);
+void   ucopy(struct video *, struct video *);
+void   uline(int, struct video *, struct video *);
+void   hash(struct video *);
+
+
+int    sgarbf = TRUE;          /* TRUE if screen is garbage.    */
+int    vtrow = HUGE;           /* Virtual cursor row.           */
+int    vtcol = HUGE;           /* Virtual cursor column.        */
+int    tthue = CNONE;          /* Current color.                */
+int    ttrow = HUGE;           /* Physical cursor row.          */
+int    ttcol = HUGE;           /* Physical cursor column.       */
+int    tttop = HUGE;           /* Top of scroll region.         */
+int    ttbot = HUGE;           /* Bottom of scroll region.      */
+int    lbound = 0;             /* leftmost bound of the current */
+                               /* line being displayed          */
+
+struct video   **vscreen;              /* Edge vector, virtual.         */
+struct video   **pscreen;              /* Edge vector, physical.        */
+struct video    *video;                /* Actual screen data.           */
+struct video     blanks;               /* Blank line image.             */
+
+/*
+ * This matrix is written as an array because
+ * we do funny things in the "setscores" routine, which
+ * is very compute intensive, to make the subscripts go away.
+ * It would be "SCORE  score[NROW][NROW]" in old speak.
+ * Look at "setscores" to understand what is up.
+ */
+struct score *score;                   /* [NROW * NROW] */
+
+#ifndef LINENOMODE
+#define LINENOMODE TRUE
+#endif /* !LINENOMODE */
+static int      linenos = LINENOMODE;
+
+/* Is macro recording enabled? */
+extern int macrodef;
+/* Is working directory global? */
+extern int globalwd;
+
+/*
+ * Since we don't have variables (we probably should) these are command
+ * processors for changing the values of mode flags.
+ */
+/* ARGSUSED */
+int
+linenotoggle(int f, int n)
+{
+       if (f & FFARG)
+               linenos = n > 0;
+       else
+               linenos = !linenos;
+
+       sgarbf = TRUE;
+
+       return (TRUE);
+}
+
+/*
+ * Reinit the display data structures, this is called when the terminal
+ * size changes.
+ */
+int
+vtresize(int force, int newrow, int newcol)
+{
+       int      i;
+       int      rowchanged, colchanged;
+       static   int first_run = 1;
+       struct video    *vp;
+
+       if (newrow < 1 || newcol < 1)
+               return (FALSE);
+
+       rowchanged = (newrow != nrow);
+       colchanged = (newcol != ncol);
+
+#define TRYREALLOC(a, n) do {                                  \
+               void *tmp;                                      \
+               if ((tmp = realloc((a), (n))) == NULL) {        \
+                       panic("out of memory in display code"); \
+               }                                               \
+               (a) = tmp;                                      \
+       } while (0)
+
+       /* No update needed */
+       if (!first_run && !force && !rowchanged && !colchanged)
+               return (TRUE);
+
+       if (first_run)
+               memset(&blanks, 0, sizeof(blanks));
+
+       if (rowchanged || first_run) {
+               int vidstart;
+
+               /*
+                * This is not pretty.
+                */
+               if (nrow == 0)
+                       vidstart = 0;
+               else
+                       vidstart = 2 * (nrow - 1);
+
+               /*
+                * We're shrinking, free some internal data.
+                */
+               if (newrow < nrow) {
+                       for (i = 2 * (newrow - 1); i < 2 * (nrow - 1); i++) {
+                               free(video[i].v_text);
+                               video[i].v_text = NULL;
+                       }
+               }
+
+               TRYREALLOC(score, newrow * newrow * sizeof(struct score));
+               TRYREALLOC(vscreen, (newrow - 1) * sizeof(struct video *));
+               TRYREALLOC(pscreen, (newrow - 1) * sizeof(struct video *));
+               TRYREALLOC(video, (2 * (newrow - 1)) * sizeof(struct video));
+
+               /*
+                * Zero-out the entries we just allocated.
+                */
+               for (i = vidstart; i < 2 * (newrow - 1); i++)
+                       memset(&video[i], 0, sizeof(struct video));
+
+               /*
+                * Reinitialize vscreen and pscreen arrays completely.
+                */
+               vp = &video[0];
+               for (i = 0; i < newrow - 1; ++i) {
+                       vscreen[i] = vp;
+                       ++vp;
+                       pscreen[i] = vp;
+                       ++vp;
+               }
+       }
+       if (rowchanged || colchanged || first_run) {
+               for (i = 0; i < 2 * (newrow - 1); i++)
+                       TRYREALLOC(video[i].v_text, newcol * sizeof(char));
+               TRYREALLOC(blanks.v_text, newcol * sizeof(char));
+       }
+
+       nrow = newrow;
+       ncol = newcol;
+
+       if (ttrow > nrow)
+               ttrow = nrow;
+       if (ttcol > ncol)
+               ttcol = ncol;
+
+       first_run = 0;
+       return (TRUE);
+}
+
+#undef TRYREALLOC
+
+/*
+ * Initialize the data structures used
+ * by the display code. The edge vectors used
+ * to access the screens are set up. The operating
+ * system's terminal I/O channel is set up. Fill the
+ * "blanks" array with ASCII blanks. The rest is done
+ * at compile time. The original window is marked
+ * as needing full update, and the physical screen
+ * is marked as garbage, so all the right stuff happens
+ * on the first call to redisplay.
+ */
+void
+vtinit(void)
+{
+       int     i;
+
+       ttopen();
+       ttinit();
+
+       /*
+        * ttinit called ttresize(), which called vtresize(), so our data
+        * structures are setup correctly.
+        */
+
+       blanks.v_color = CTEXT;
+       for (i = 0; i < ncol; ++i)
+               blanks.v_text[i] = ' ';
+}
+
+/*
+ * Tidy up the virtual display system
+ * in anticipation of a return back to the host
+ * operating system. Right now all we do is position
+ * the cursor to the last line, erase the line, and
+ * close the terminal channel.
+ */
+void
+vttidy(void)
+{
+       ttcolor(CTEXT);
+       ttnowindow();           /* No scroll window.     */
+       ttmove(nrow - 1, 0);    /* Echo line.            */
+       tteeol();
+       tttidy();
+       ttflush();
+       ttclose();
+}
+
+/*
+ * Move the virtual cursor to an origin
+ * 0 spot on the virtual display screen. I could
+ * store the column as a character pointer to the spot
+ * on the line, which would make "vtputc" a little bit
+ * more efficient. No checking for errors.
+ */
+void
+vtmove(int row, int col)
+{
+       vtrow = row;
+       vtcol = col;
+}
+
+/*
+ * Write a character to the virtual display,
+ * dealing with long lines and the display of unprintable
+ * things like control characters. Also expand tabs every 8
+ * columns. This code only puts printing characters into
+ * the virtual display image. Special care must be taken when
+ * expanding tabs. On a screen whose width is not a multiple
+ * of 8, it is possible for the virtual cursor to hit the
+ * right margin before the next tab stop is reached. This
+ * makes the tab code loop if you are not careful.
+ * Three guesses how we found this.
+ */
+void
+vtputc(int c)
+{
+       struct video    *vp;
+
+       c &= 0xff;
+
+       vp = vscreen[vtrow];
+       if (vtcol >= ncol)
+               vp->v_text[ncol - 1] = '$';
+       else if (c == '\t'
+#ifdef NOTAB
+           && !(curbp->b_flag & BFNOTAB)
+#endif
+           ) {
+               do {
+                       vtputc(' ');
+               } while (vtcol < ncol && (vtcol & 0x07) != 0);
+       } else if (ISCTRL(c)) {
+               vtputc('^');
+               vtputc(CCHR(c));
+       } else if (isprint(c))
+               vp->v_text[vtcol++] = c;
+       else {
+               char bf[5];
+
+               snprintf(bf, sizeof(bf), "\\%o", c);
+               vtputs(bf);
+       }
+}
+
+/*
+ * Put a character to the virtual screen in an extended line.  If we are not
+ * yet on left edge, don't print it yet.  Check for overflow on the right
+ * margin.
+ */
+void
+vtpute(int c)
+{
+       struct video *vp;
+
+       c &= 0xff;
+
+       vp = vscreen[vtrow];
+       if (vtcol >= ncol)
+               vp->v_text[ncol - 1] = '$';
+       else if (c == '\t'
+#ifdef NOTAB
+           && !(curbp->b_flag & BFNOTAB)
+#endif
+           ) {
+               do {
+                       vtpute(' ');
+               } while (((vtcol + lbound) & 0x07) != 0 && vtcol < ncol);
+       } else if (ISCTRL(c) != FALSE) {
+               vtpute('^');
+               vtpute(CCHR(c));
+       } else {
+               if (vtcol >= 0)
+                       vp->v_text[vtcol] = c;
+               ++vtcol;
+       }
+}
+
+/*
+ * Erase from the end of the software cursor to the end of the line on which
+ * the software cursor is located. The display routines will decide if a
+ * hardware erase to end of line command should be used to display this.
+ */
+void
+vteeol(void)
+{
+       struct video *vp;
+
+       vp = vscreen[vtrow];
+       while (vtcol < ncol)
+               vp->v_text[vtcol++] = ' ';
+}
+
+/*
+ * Make sure that the display is
+ * right. This is a three part process. First,
+ * scan through all of the windows looking for dirty
+ * ones. Check the framing, and refresh the screen.
+ * Second, make sure that "currow" and "curcol" are
+ * correct for the current window. Third, make the
+ * virtual and physical screens the same.
+ */
+void
+update(void)
+{
+       struct line     *lp;
+       struct mgwin    *wp;
+       struct video    *vp1;
+       struct video    *vp2;
+       int      c, i, j;
+       int      hflag;
+       int      currow, curcol;
+       int      offs, size;
+
+       if (charswaiting())
+               return;
+       if (sgarbf) {           /* must update everything */
+               wp = wheadp;
+               while (wp != NULL) {
+                       wp->w_rflag |= WFMODE | WFFULL;
+                       wp = wp->w_wndp;
+               }
+       }
+       if (linenos) {
+               wp = wheadp;
+               while (wp != NULL) {
+                       wp->w_rflag |= WFMODE;
+                       wp = wp->w_wndp;
+               }
+       }
+       hflag = FALSE;                  /* Not hard. */
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+               /*
+                * Nothing to be done.
+                */
+               if (wp->w_rflag == 0)
+                       continue;
+
+               if ((wp->w_rflag & WFFRAME) == 0) {
+                       lp = wp->w_linep;
+                       for (i = 0; i < wp->w_ntrows; ++i) {
+                               if (lp == wp->w_dotp)
+                                       goto out;
+                               if (lp == wp->w_bufp->b_headp)
+                                       break;
+                               lp = lforw(lp);
+                       }
+               }
+               /*
+                * Put the middle-line in place.
+                */
+               i = wp->w_frame;
+               if (i > 0) {
+                       --i;
+                       if (i >= wp->w_ntrows)
+                               i = wp->w_ntrows - 1;
+               } else if (i < 0) {
+                       i += wp->w_ntrows;
+                       if (i < 0)
+                               i = 0;
+               } else
+                       i = wp->w_ntrows / 2; /* current center, no change */
+
+               /*
+                * Find the line.
+                */
+               lp = wp->w_dotp;
+               while (i != 0 && lback(lp) != wp->w_bufp->b_headp) {
+                       --i;
+                       lp = lback(lp);
+               }
+               wp->w_linep = lp;
+               wp->w_rflag |= WFFULL;  /* Force full.           */
+       out:
+               lp = wp->w_linep;       /* Try reduced update.   */
+               i = wp->w_toprow;
+               if ((wp->w_rflag & ~WFMODE) == WFEDIT) {
+                       while (lp != wp->w_dotp) {
+                               ++i;
+                               lp = lforw(lp);
+                       }
+                       vscreen[i]->v_color = CTEXT;
+                       vscreen[i]->v_flag |= (VFCHG | VFHBAD);
+                       vtmove(i, 0);
+                       for (j = 0; j < llength(lp); ++j)
+                               vtputc(lgetc(lp, j));
+                       vteeol();
+               } else if ((wp->w_rflag & (WFEDIT | WFFULL)) != 0) {
+                       hflag = TRUE;
+                       while (i < wp->w_toprow + wp->w_ntrows) {
+                               vscreen[i]->v_color = CTEXT;
+                               vscreen[i]->v_flag |= (VFCHG | VFHBAD);
+                               vtmove(i, 0);
+                               if (lp != wp->w_bufp->b_headp) {
+                                       for (j = 0; j < llength(lp); ++j)
+                                               vtputc(lgetc(lp, j));
+                                       lp = lforw(lp);
+                               }
+                               vteeol();
+                               ++i;
+                       }
+               }
+               if ((wp->w_rflag & WFMODE) != 0)
+                       modeline(wp);
+               wp->w_rflag = 0;
+               wp->w_frame = 0;
+       }
+       lp = curwp->w_linep;    /* Cursor location. */
+       currow = curwp->w_toprow;
+       while (lp != curwp->w_dotp) {
+               ++currow;
+               lp = lforw(lp);
+       }
+       curcol = 0;
+       i = 0;
+       while (i < curwp->w_doto) {
+               c = lgetc(lp, i++);
+               if (c == '\t'
+#ifdef NOTAB
+                   && !(curbp->b_flag & BFNOTAB)
+#endif
+                       ) {
+                       curcol |= 0x07;
+                       curcol++;
+               } else if (ISCTRL(c) != FALSE)
+                       curcol += 2;
+               else if (isprint(c))
+                       curcol++;
+               else {
+                       char bf[5];
+
+                       snprintf(bf, sizeof(bf), "\\%o", c);
+                       curcol += strlen(bf);
+               }
+       }
+       if (curcol >= ncol - 1) {       /* extended line. */
+               /* flag we are extended and changed */
+               vscreen[currow]->v_flag |= VFEXT | VFCHG;
+               updext(currow, curcol); /* and output extended line */
+       } else
+               lbound = 0;     /* not extended line */
+
+       /*
+        * Make sure no lines need to be de-extended because the cursor is no
+        * longer on them.
+        */
+       wp = wheadp;
+       while (wp != NULL) {
+               lp = wp->w_linep;
+               i = wp->w_toprow;
+               while (i < wp->w_toprow + wp->w_ntrows) {
+                       if (vscreen[i]->v_flag & VFEXT) {
+                               /* always flag extended lines as changed */
+                               vscreen[i]->v_flag |= VFCHG;
+                               if ((wp != curwp) || (lp != wp->w_dotp) ||
+                                   (curcol < ncol - 1)) {
+                                       vtmove(i, 0);
+                                       for (j = 0; j < llength(lp); ++j)
+                                               vtputc(lgetc(lp, j));
+                                       vteeol();
+                                       /* this line no longer is extended */
+                                       vscreen[i]->v_flag &= ~VFEXT;
+                               }
+                       }
+                       lp = lforw(lp);
+                       ++i;
+               }
+               /* if garbaged then fix up mode lines */
+               if (sgarbf != FALSE)
+                       vscreen[i]->v_flag |= VFCHG;
+               /* and onward to the next window */
+               wp = wp->w_wndp;
+       }
+
+       if (sgarbf != FALSE) {  /* Screen is garbage.    */
+               sgarbf = FALSE; /* Erase-page clears.    */
+               epresf = FALSE; /* The message area.     */
+               tttop = HUGE;   /* Forget where you set. */
+               ttbot = HUGE;   /* scroll region.        */
+               tthue = CNONE;  /* Color unknown.        */
+               ttmove(0, 0);
+               tteeop();
+               for (i = 0; i < nrow - 1; ++i) {
+                       uline(i, vscreen[i], &blanks);
+                       ucopy(vscreen[i], pscreen[i]);
+               }
+               ttmove(currow, curcol - lbound);
+               ttflush();
+               return;
+       }
+       if (hflag != FALSE) {                   /* Hard update?         */
+               for (i = 0; i < nrow - 1; ++i) {/* Compute hash data.   */
+                       hash(vscreen[i]);
+                       hash(pscreen[i]);
+               }
+               offs = 0;                       /* Get top match.       */
+               while (offs != nrow - 1) {
+                       vp1 = vscreen[offs];
+                       vp2 = pscreen[offs];
+                       if (vp1->v_color != vp2->v_color
+                           || vp1->v_hash != vp2->v_hash)
+                               break;
+                       uline(offs, vp1, vp2);
+                       ucopy(vp1, vp2);
+                       ++offs;
+               }
+               if (offs == nrow - 1) {         /* Might get it all.    */
+                       ttmove(currow, curcol - lbound);
+                       ttflush();
+                       return;
+               }
+               size = nrow - 1;                /* Get bottom match.    */
+               while (size != offs) {
+                       vp1 = vscreen[size - 1];
+                       vp2 = pscreen[size - 1];
+                       if (vp1->v_color != vp2->v_color
+                           || vp1->v_hash != vp2->v_hash)
+                               break;
+                       uline(size - 1, vp1, vp2);
+                       ucopy(vp1, vp2);
+                       --size;
+               }
+               if ((size -= offs) == 0)        /* Get screen size.     */
+                       panic("Illegal screen size in update");
+               setscores(offs, size);          /* Do hard update.      */
+               traceback(offs, size, size, size);
+               for (i = 0; i < size; ++i)
+                       ucopy(vscreen[offs + i], pscreen[offs + i]);
+               ttmove(currow, curcol - lbound);
+               ttflush();
+               return;
+       }
+       for (i = 0; i < nrow - 1; ++i) {        /* Easy update.         */
+               vp1 = vscreen[i];
+               vp2 = pscreen[i];
+               if ((vp1->v_flag & VFCHG) != 0) {
+                       uline(i, vp1, vp2);
+                       ucopy(vp1, vp2);
+               }
+       }
+       ttmove(currow, curcol - lbound);
+       ttflush();
+}
+
+/*
+ * Update a saved copy of a line,
+ * kept in a video structure. The "vvp" is
+ * the one in the "vscreen". The "pvp" is the one
+ * in the "pscreen". This is called to make the
+ * virtual and physical screens the same when
+ * display has done an update.
+ */
+void
+ucopy(struct video *vvp, struct video *pvp)
+{
+       vvp->v_flag &= ~VFCHG;          /* Changes done.         */
+       pvp->v_flag = vvp->v_flag;      /* Update model.         */
+       pvp->v_hash = vvp->v_hash;
+       pvp->v_cost = vvp->v_cost;
+       pvp->v_color = vvp->v_color;
+       bcopy(vvp->v_text, pvp->v_text, ncol);
+}
+
+/*
+ * updext: update the extended line which the cursor is currently on at a
+ * column greater than the terminal width. The line will be scrolled right or
+ * left to let the user see where the cursor is.
+ */
+void
+updext(int currow, int curcol)
+{
+       struct line     *lp;                    /* pointer to current line */
+       int      j;                     /* index into line */
+
+       if (ncol < 2)
+               return;
+
+       /*
+        * calculate what column the left bound should be
+        * (force cursor into middle half of screen)
+        */
+       lbound = curcol - (curcol % (ncol >> 1)) - (ncol >> 2);
+
+       /*
+        * scan through the line outputing characters to the virtual screen
+        * once we reach the left edge
+        */
+       vtmove(currow, -lbound);                /* start scanning offscreen */
+       lp = curwp->w_dotp;                     /* line to output */
+       for (j = 0; j < llength(lp); ++j)       /* until the end-of-line */
+               vtpute(lgetc(lp, j));
+       vteeol();                               /* truncate the virtual line */
+       vscreen[currow]->v_text[0] = '$';       /* and put a '$' in column 1 */
+}
+
+/*
+ * Update a single line. This routine only
+ * uses basic functionality (no insert and delete character,
+ * but erase to end of line). The "vvp" points at the video
+ * structure for the line on the virtual screen, and the "pvp"
+ * is the same for the physical screen. Avoid erase to end of
+ * line when updating CMODE color lines, because of the way that
+ * reverse video works on most terminals.
+ */
+void
+uline(int row, struct video *vvp, struct video *pvp)
+{
+       char  *cp1;
+       char  *cp2;
+       char  *cp3;
+       char  *cp4;
+       char  *cp5;
+       int    nbflag;
+
+       if (vvp->v_color != pvp->v_color) {     /* Wrong color, do a     */
+               ttmove(row, 0);                 /* full redraw.          */
+#ifdef STANDOUT_GLITCH
+               if (pvp->v_color != CTEXT && magic_cookie_glitch >= 0)
+                       tteeol();
+#endif
+               ttcolor(vvp->v_color);
+#ifdef STANDOUT_GLITCH
+               cp1 = &vvp->v_text[magic_cookie_glitch > 0 ? magic_cookie_glitch : 0];
+               /*
+                * The odd code for magic_cookie_glitch==0 is to avoid
+                * putting the invisible glitch character on the next line.
+                * (Hazeltine executive 80 model 30)
+                */
+               cp2 = &vvp->v_text[ncol - (magic_cookie_glitch >= 0 ?
+                   (magic_cookie_glitch != 0 ? magic_cookie_glitch : 1) : 0)];
+#else
+               cp1 = &vvp->v_text[0];
+               cp2 = &vvp->v_text[ncol];
+#endif
+               while (cp1 != cp2) {
+                       ttputc(*cp1++);
+                       ++ttcol;
+               }
+#ifndef MOVE_STANDOUT
+               ttcolor(CTEXT);
+#endif
+               return;
+       }
+       cp1 = &vvp->v_text[0];          /* Compute left match.   */
+       cp2 = &pvp->v_text[0];
+       while (cp1 != &vvp->v_text[ncol] && cp1[0] == cp2[0]) {
+               ++cp1;
+               ++cp2;
+       }
+       if (cp1 == &vvp->v_text[ncol])  /* All equal.            */
+               return;
+       nbflag = FALSE;
+       cp3 = &vvp->v_text[ncol];       /* Compute right match.  */
+       cp4 = &pvp->v_text[ncol];
+       while (cp3[-1] == cp4[-1]) {
+               --cp3;
+               --cp4;
+               if (cp3[0] != ' ')      /* Note non-blanks in    */
+                       nbflag = TRUE;  /* the right match.      */
+       }
+       cp5 = cp3;                      /* Is erase good?        */
+       if (nbflag == FALSE && vvp->v_color == CTEXT) {
+               while (cp5 != cp1 && cp5[-1] == ' ')
+                       --cp5;
+               /* Alcyon hack */
+               if ((int) (cp3 - cp5) <= tceeol)
+                       cp5 = cp3;
+       }
+       /* Alcyon hack */
+       ttmove(row, (int) (cp1 - &vvp->v_text[0]));
+#ifdef STANDOUT_GLITCH
+       if (vvp->v_color != CTEXT && magic_cookie_glitch > 0) {
+               if (cp1 < &vvp->v_text[magic_cookie_glitch])
+                       cp1 = &vvp->v_text[magic_cookie_glitch];
+               if (cp5 > &vvp->v_text[ncol - magic_cookie_glitch])
+                       cp5 = &vvp->v_text[ncol - magic_cookie_glitch];
+       } else if (magic_cookie_glitch < 0)
+#endif
+               ttcolor(vvp->v_color);
+       while (cp1 != cp5) {
+               ttputc(*cp1++);
+               ++ttcol;
+       }
+       if (cp5 != cp3)                 /* Do erase.             */
+               tteeol();
+}
+
+/*
+ * Redisplay the mode line for the window pointed to by the "wp".
+ * This is the only routine that has any idea of how the mode line is
+ * formatted. You can change the modeline format by hacking at this
+ * routine. Called by "update" any time there is a dirty window.  Note
+ * that if STANDOUT_GLITCH is defined, first and last magic_cookie_glitch
+ * characters may never be seen.
+ */
+void
+modeline(struct mgwin *wp)
+{
+       int     n, md;
+       struct buffer *bp;
+       char sl[21];            /* Overkill. Space for 2^64 in base 10. */
+       int len;
+
+       n = wp->w_toprow + wp->w_ntrows;        /* Location.             */
+       vscreen[n]->v_color = CMODE;            /* Mode line color.      */
+       vscreen[n]->v_flag |= (VFCHG | VFHBAD); /* Recompute, display.   */
+       vtmove(n, 0);                           /* Seek to right line.   */
+       bp = wp->w_bufp;
+       vtputc('-');
+       vtputc('-');
+       if ((bp->b_flag & BFREADONLY) != 0) {
+               vtputc('%');
+               if ((bp->b_flag & BFCHG) != 0)
+                       vtputc('*');
+               else
+                       vtputc('%');
+       } else if ((bp->b_flag & BFCHG) != 0) { /* "*" if changed.       */
+               vtputc('*');
+               vtputc('*');
+       } else {
+               vtputc('-');
+               vtputc('-');
+       }
+       vtputc('-');
+       n = 5;
+       n += vtputs("Mg: ");
+       if (bp->b_bname[0] != '\0')
+               n += vtputs(&(bp->b_bname[0]));
+       while (n < 42) {                        /* Pad out with blanks.  */
+               vtputc(' ');
+               ++n;
+       }
+       vtputc('(');
+       ++n;
+       for (md = 0; ; ) {
+               n += vtputs(bp->b_modes[md]->p_name);
+               if (++md > bp->b_nmodes)
+                       break;
+               vtputc('-');
+               ++n;
+       }
+       /* XXX These should eventually move to a real mode */
+       if (macrodef == TRUE)
+               n += vtputs("-def");
+       if (globalwd == TRUE)
+               n += vtputs("-gwd");
+       vtputc(')');
+       ++n;
+
+       if (linenos) {
+               len = snprintf(sl, sizeof(sl), "--L%d--C%d", wp->w_dotline,
+                   getcolpos());
+               if (len < sizeof(sl) && len != -1)
+                       n += vtputs(sl);
+       }
+
+       while (n < ncol) {                      /* Pad out.              */
+               vtputc('-');
+               ++n;
+       }
+}
+
+/*
+ * Output a string to the mode line, report how long it was.
+ */
+int
+vtputs(const char *s)
+{
+       int n = 0;
+
+       while (*s != '\0') {
+               vtputc(*s++);
+               ++n;
+       }
+       return (n);
+}
+
+/*
+ * Compute the hash code for the line pointed to by the "vp".
+ * Recompute it if necessary. Also set the approximate redisplay
+ * cost. The validity of the hash code is marked by a flag bit.
+ * The cost understand the advantages of erase to end of line.
+ * Tuned for the VAX by Bob McNamara; better than it used to be on
+ * just about any machine.
+ */
+void
+hash(struct video *vp)
+{
+       int     i, n;
+       char   *s;
+
+       if ((vp->v_flag & VFHBAD) != 0) {       /* Hash bad.             */
+               s = &vp->v_text[ncol - 1];
+               for (i = ncol; i != 0; --i, --s)
+                       if (*s != ' ')
+                               break;
+               n = ncol - i;                   /* Erase cheaper?        */
+               if (n > tceeol)
+                       n = tceeol;
+               vp->v_cost = i + n;             /* Bytes + blanks.       */
+               for (n = 0; i != 0; --i, --s)
+                       n = (n << 5) + n + *s;
+               vp->v_hash = n;                 /* Hash code.            */
+               vp->v_flag &= ~VFHBAD;          /* Flag as all done.     */
+       }
+}
+
+/*
+ * Compute the Insert-Delete
+ * cost matrix. The dynamic programming algorithm
+ * described by James Gosling is used. This code assumes
+ * that the line above the echo line is the last line involved
+ * in the scroll region. This is easy to arrange on the VT100
+ * because of the scrolling region. The "offs" is the origin 0
+ * offset of the first row in the virtual/physical screen that
+ * is being updated; the "size" is the length of the chunk of
+ * screen being updated. For a full screen update, use offs=0
+ * and size=nrow-1.
+ *
+ * Older versions of this code implemented the score matrix by
+ * a two dimensional array of SCORE nodes. This put all kinds of
+ * multiply instructions in the code! This version is written to
+ * use a linear array and pointers, and contains no multiplication
+ * at all. The code has been carefully looked at on the VAX, with
+ * only marginal checking on other machines for efficiency. In
+ * fact, this has been tuned twice! Bob McNamara tuned it even
+ * more for the VAX, which is a big issue for him because of
+ * the 66 line X displays.
+ *
+ * On some machines, replacing the "for (i=1; i<=size; ++i)" with
+ * i = 1; do { } while (++i <=size)" will make the code quite a
+ * bit better; but it looks ugly.
+ */
+void
+setscores(int offs, int size)
+{
+       struct score     *sp;
+       struct score     *sp1;
+       struct video    **vp, **pp;
+       struct video    **vbase, **pbase;
+       int       tempcost;
+       int       bestcost;
+       int       j, i;
+
+       vbase = &vscreen[offs - 1];     /* By hand CSE's.        */
+       pbase = &pscreen[offs - 1];
+       score[0].s_itrace = 0;          /* [0, 0]                */
+       score[0].s_jtrace = 0;
+       score[0].s_cost = 0;
+       sp = &score[1];                 /* Row 0, inserts.       */
+       tempcost = 0;
+       vp = &vbase[1];
+       for (j = 1; j <= size; ++j) {
+               sp->s_itrace = 0;
+               sp->s_jtrace = j - 1;
+               tempcost += tcinsl;
+               tempcost += (*vp)->v_cost;
+               sp->s_cost = tempcost;
+               ++vp;
+               ++sp;
+       }
+       sp = &score[nrow];              /* Column 0, deletes.    */
+       tempcost = 0;
+       for (i = 1; i <= size; ++i) {
+               sp->s_itrace = i - 1;
+               sp->s_jtrace = 0;
+               tempcost += tcdell;
+               sp->s_cost = tempcost;
+               sp += nrow;
+       }
+       sp1 = &score[nrow + 1];         /* [1, 1].               */
+       pp = &pbase[1];
+       for (i = 1; i <= size; ++i) {
+               sp = sp1;
+               vp = &vbase[1];
+               for (j = 1; j <= size; ++j) {
+                       sp->s_itrace = i - 1;
+                       sp->s_jtrace = j;
+                       bestcost = (sp - nrow)->s_cost;
+                       if (j != size)  /* Cd(A[i])=0 @ Dis.     */
+                               bestcost += tcdell;
+                       tempcost = (sp - 1)->s_cost;
+                       tempcost += (*vp)->v_cost;
+                       if (i != size)  /* Ci(B[j])=0 @ Dsj.     */
+                               tempcost += tcinsl;
+                       if (tempcost < bestcost) {
+                               sp->s_itrace = i;
+                               sp->s_jtrace = j - 1;
+                               bestcost = tempcost;
+                       }
+                       tempcost = (sp - nrow - 1)->s_cost;
+                       if ((*pp)->v_color != (*vp)->v_color
+                           || (*pp)->v_hash != (*vp)->v_hash)
+                               tempcost += (*vp)->v_cost;
+                       if (tempcost < bestcost) {
+                               sp->s_itrace = i - 1;
+                               sp->s_jtrace = j - 1;
+                               bestcost = tempcost;
+                       }
+                       sp->s_cost = bestcost;
+                       ++sp;           /* Next column.          */
+                       ++vp;
+               }
+               ++pp;
+               sp1 += nrow;            /* Next row.             */
+       }
+}
+
+/*
+ * Trace back through the dynamic programming cost
+ * matrix, and update the screen using an optimal sequence
+ * of redraws, insert lines, and delete lines. The "offs" is
+ * the origin 0 offset of the chunk of the screen we are about to
+ * update. The "i" and "j" are always started in the lower right
+ * corner of the matrix, and imply the size of the screen.
+ * A full screen traceback is called with offs=0 and i=j=nrow-1.
+ * There is some do-it-yourself double subscripting here,
+ * which is acceptable because this routine is much less compute
+ * intensive then the code that builds the score matrix!
+ */
+void
+traceback(int offs, int size, int i, int j)
+{
+       int     itrace, jtrace;
+       int     k;
+       int     ninsl, ndraw, ndell;
+
+       if (i == 0 && j == 0)   /* End of update.        */
+               return;
+       itrace = score[(nrow * i) + j].s_itrace;
+       jtrace = score[(nrow * i) + j].s_jtrace;
+       if (itrace == i) {      /* [i, j-1]              */
+               ninsl = 0;      /* Collect inserts.      */
+               if (i != size)
+                       ninsl = 1;
+               ndraw = 1;
+               while (itrace != 0 || jtrace != 0) {
+                       if (score[(nrow * itrace) + jtrace].s_itrace != itrace)
+                               break;
+                       jtrace = score[(nrow * itrace) + jtrace].s_jtrace;
+                       if (i != size)
+                               ++ninsl;
+                       ++ndraw;
+               }
+               traceback(offs, size, itrace, jtrace);
+               if (ninsl != 0) {
+                       ttcolor(CTEXT);
+                       ttinsl(offs + j - ninsl, offs + size - 1, ninsl);
+               }
+               do {            /* B[j], A[j] blank.     */
+                       k = offs + j - ndraw;
+                       uline(k, vscreen[k], &blanks);
+               } while (--ndraw);
+               return;
+       }
+       if (jtrace == j) {      /* [i-1, j]              */
+               ndell = 0;      /* Collect deletes.      */
+               if (j != size)
+                       ndell = 1;
+               while (itrace != 0 || jtrace != 0) {
+                       if (score[(nrow * itrace) + jtrace].s_jtrace != jtrace)
+                               break;
+                       itrace = score[(nrow * itrace) + jtrace].s_itrace;
+                       if (j != size)
+                               ++ndell;
+               }
+               if (ndell != 0) {
+                       ttcolor(CTEXT);
+                       ttdell(offs + i - ndell, offs + size - 1, ndell);
+               }
+               traceback(offs, size, itrace, jtrace);
+               return;
+       }
+       traceback(offs, size, itrace, jtrace);
+       k = offs + j - 1;
+       uline(k, vscreen[k], pscreen[offs + i - 1]);
+}
diff --git a/mg/echo.c b/mg/echo.c
new file mode 100644 (file)
index 0000000..ce173f6
--- /dev/null
+++ b/mg/echo.c
@@ -0,0 +1,964 @@
+/*     $OpenBSD: echo.c,v 1.50 2012/04/12 04:47:59 lum Exp $   */
+
+/* This file is in the public domain. */
+
+/*
+ *     Echo line reading and writing.
+ *
+ * Common routines for reading and writing characters in the echo line area
+ * of the display screen. Used by the entire known universe.
+ */
+
+#include "def.h"
+#include "key.h"
+#include "macro.h"
+
+#include "funmap.h"
+
+#include <stdarg.h>
+#include <term.h>
+
+static char    *veread(const char *, char *, size_t, int, va_list);
+static int      complt(int, int, char *, size_t, int, int *);
+static int      complt_list(int, char *, int);
+static void     eformat(const char *, va_list);
+static void     eputi(int, int);
+static void     eputl(long, int);
+static void     eputs(const char *);
+static void     eputc(char);
+static struct list     *copy_list(struct list *);
+
+int            epresf = FALSE;         /* stuff in echo line flag */
+
+/*
+ * Erase the echo line.
+ */
+void
+eerase(void)
+{
+       ttcolor(CTEXT);
+       ttmove(nrow - 1, 0);
+       tteeol();
+       ttflush();
+       epresf = FALSE;
+}
+
+/*
+ * Ask a "yes" or "no" question.  Return ABORT if the user answers the
+ * question with the abort ("^G") character.  Return FALSE for "no" and
+ * TRUE for "yes".  No formatting services are available.  No newline
+ * required.
+ */
+int
+eyorn(const char *sp)
+{
+       int      s;
+
+       if (inmacro)
+               return (TRUE);
+
+       ewprintf("%s? (y or n) ", sp);
+       for (;;) {
+               s = getkey(FALSE);
+               if (s == 'y' || s == 'Y' || s == ' ')
+                       return (TRUE);
+               if (s == 'n' || s == 'N' || s == CCHR('M'))
+                       return (FALSE);
+               if (s == CCHR('G'))
+                       return (ctrlg(FFRAND, 1));
+               ewprintf("Please answer y or n.  %s? (y or n) ", sp);
+       }
+       /* NOTREACHED */
+}
+
+/*
+ * Like eyorn, but for more important questions.  User must type all of
+ * "yes" or "no" and the trailing newline.
+ */
+int
+eyesno(const char *sp)
+{
+       char     buf[64], *rep;
+
+       if (inmacro)
+               return (TRUE);
+
+       rep = eread("%s? (yes or no) ", buf, sizeof(buf),
+           EFNUL | EFNEW | EFCR, sp);
+       for (;;) {
+               if (rep == NULL)
+                       return (ABORT);
+               if (rep[0] != '\0') {
+                       if (macrodef) {
+                               struct line     *lp = maclcur;
+
+                               maclcur = lp->l_bp;
+                               maclcur->l_fp = lp->l_fp;
+                               free(lp);
+                       }
+                       if ((rep[0] == 'y' || rep[0] == 'Y') &&
+                           (rep[1] == 'e' || rep[1] == 'E') &&
+                           (rep[2] == 's' || rep[2] == 'S') &&
+                           (rep[3] == '\0'))
+                               return (TRUE);
+                       if ((rep[0] == 'n' || rep[0] == 'N') &&
+                           (rep[1] == 'o' || rep[0] == 'O') &&
+                           (rep[2] == '\0'))
+                               return (FALSE);
+               }
+               rep = eread("Please answer yes or no.  %s? (yes or no) ",
+                   buf, sizeof(buf), EFNUL | EFNEW | EFCR, sp);
+       }
+       /* NOTREACHED */
+}
+
+/*
+ * This is the general "read input from the echo line" routine.  The basic
+ * idea is that the prompt string "prompt" is written to the echo line, and
+ * a one line reply is read back into the supplied "buf" (with maximum
+ * length "len").
+ * XXX: When checking for an empty return value, always check rep, *not* buf
+ * as buf may be freed in pathological cases.
+ */
+/* VARARGS */
+char *
+eread(const char *fmt, char *buf, size_t nbuf, int flag, ...)
+{
+       va_list  ap;
+       char    *rep;
+
+       va_start(ap, flag);
+       rep = veread(fmt, buf, nbuf, flag, ap);
+       va_end(ap);
+       return (rep);
+}
+
+static char *
+veread(const char *fp, char *buf, size_t nbuf, int flag, va_list ap)
+{
+       int      dynbuf = (buf == NULL);
+       int      cpos, epos;            /* cursor, end position in buf */
+       int      c, i, y;
+       int      cplflag = FALSE;       /* display completion list */
+       int      cwin = FALSE;          /* completion list created */
+       int      mr = 0;                /* match left arrow */
+       int      ml = 0;                /* match right arrow */
+       int      esc = 0;               /* position in esc pattern */
+       struct buffer   *bp;                    /* completion list buffer */
+       struct mgwin    *wp;                    /* window for compl list */
+       int      match;                 /* esc match found */
+       int      cc, rr;                /* saved ttcol, ttrow */
+       char    *ret;                   /* return value */
+
+       static char emptyval[] = "";    /* XXX hackish way to return err msg*/
+
+       if (inmacro) {
+               if (dynbuf) {
+                       if ((buf = malloc(maclcur->l_used + 1)) == NULL)
+                               return (NULL);
+               } else if (maclcur->l_used >= nbuf)
+                       return (NULL);
+               bcopy(maclcur->l_text, buf, maclcur->l_used);
+               buf[maclcur->l_used] = '\0';
+               maclcur = maclcur->l_fp;
+               return (buf);
+       }
+       epos = cpos = 0;
+       ml = mr = esc = 0;
+       cplflag = FALSE;
+
+       if ((flag & EFNEW) != 0 || ttrow != nrow - 1) {
+               ttcolor(CTEXT);
+               ttmove(nrow - 1, 0);
+               epresf = TRUE;
+       } else
+               eputc(' ');
+       eformat(fp, ap);
+       if ((flag & EFDEF) != 0) {
+               if (buf == NULL)
+                       return (NULL);
+               eputs(buf);
+               epos = cpos += strlen(buf);
+       }
+       tteeol();
+       ttflush();
+       for (;;) {
+               c = getkey(FALSE);
+               if ((flag & EFAUTO) != 0 && (c == ' ' || c == CCHR('I'))) {
+                       if (cplflag == TRUE) {
+                               complt_list(flag, buf, cpos);
+                               cwin = TRUE;
+                       } else if (complt(flag, c, buf, nbuf, epos, &i) == TRUE) {
+                               cplflag = TRUE;
+                               epos += i;
+                               cpos = epos;
+                       }
+                       continue;
+               }
+               cplflag = FALSE;
+
+               if (esc > 0) { /* ESC sequence started */
+                       match = 0;
+                       if (ml == esc && key_left[ml] && c == key_left[ml]) {
+                               match++;
+                               if (key_left[++ml] == '\0') {
+                                       c = CCHR('B');
+                                       esc = 0;
+                               }
+                       }
+                       if (mr == esc && key_right[mr] && c == key_right[mr]) {
+                               match++;
+                               if (key_right[++mr] == '\0') {
+                                       c = CCHR('F');
+                                       esc = 0;
+                               }
+                       }
+                       if (match == 0) {
+                               esc = 0;
+                               continue;
+                               /* hack. how do we know esc pattern is done? */
+                       }
+                       if (esc > 0) {
+                               esc++;
+                               continue;
+                       }
+               }
+               switch (c) {
+               case CCHR('A'): /* start of line */
+                       while (cpos > 0) {
+                               if (ISCTRL(buf[--cpos]) != FALSE) {
+                                       ttputc('\b');
+                                       --ttcol;
+                               }
+                               ttputc('\b');
+                               --ttcol;
+                       }
+                       ttflush();
+                       break;
+               case CCHR('D'):
+                       if (cpos != epos) {
+                               tteeol();
+                               y = buf[cpos];
+                               epos--;
+                               rr = ttrow;
+                               cc = ttcol;
+                               for (i = cpos; i < epos; i++) {
+                                       buf[i] = buf[i + 1];
+                                       eputc(buf[i]);
+                               }
+                               ttmove(rr, cc);
+                               ttflush();
+                       }
+                       break;
+               case CCHR('E'): /* end of line */
+                       while (cpos < epos) {
+                               eputc(buf[cpos++]);
+                       }
+                       ttflush();
+                       break;
+               case CCHR('B'): /* back */
+                       if (cpos > 0) {
+                               if (ISCTRL(buf[--cpos]) != FALSE) {
+                                       ttputc('\b');
+                                       --ttcol;
+                               }
+                               ttputc('\b');
+                               --ttcol;
+                               ttflush();
+                       }
+                       break;
+               case CCHR('F'): /* forw */
+                       if (cpos < epos) {
+                               eputc(buf[cpos++]);
+                               ttflush();
+                       }
+                       break;
+               case CCHR('Y'): /* yank from kill buffer */
+                       i = 0;
+                       while ((y = kremove(i++)) >= 0 && y != '\n') {
+                               int t;
+                               if (dynbuf && epos + 1 >= nbuf) {
+                                       void *newp;
+                                       size_t newsize = epos + epos + 16;
+                                       if ((newp = realloc(buf, newsize))
+                                           == NULL)
+                                               goto memfail;
+                                       buf = newp;
+                                       nbuf = newsize;
+                               }
+                               if (!dynbuf && epos + 1 >= nbuf) {
+                                       ewprintf("Line too long");
+                                       return (emptyval);
+                               }
+                               for (t = epos; t > cpos; t--)
+                                       buf[t] = buf[t - 1];
+                               buf[cpos++] = (char)y;
+                               epos++;
+                               eputc((char)y);
+                               cc = ttcol;
+                               rr = ttrow;
+                               for (t = cpos; t < epos; t++)
+                                       eputc(buf[t]);
+                               ttmove(rr, cc);
+                       }
+                       ttflush();
+                       break;
+               case CCHR('K'): /* copy here-EOL to kill buffer */
+                       kdelete();
+                       for (i = cpos; i < epos; i++)
+                               kinsert(buf[i], KFORW);
+                       tteeol();
+                       epos = cpos;
+                       ttflush();
+                       break;
+               case CCHR('['):
+                       ml = mr = esc = 1;
+                       break;
+               case CCHR('J'):
+                       c = CCHR('M');
+                       /* FALLTHROUGH */
+               case CCHR('M'):                 /* return, done */
+                       /* if there's nothing in the minibuffer, abort */
+                       if (epos == 0 && !(flag & EFNUL)) {
+                               (void)ctrlg(FFRAND, 0);
+                               ttflush();
+                               return (NULL);
+                       }
+                       if ((flag & EFFUNC) != 0) {
+                               if (complt(flag, c, buf, nbuf, epos, &i)
+                                   == FALSE)
+                                       continue;
+                               if (i > 0)
+                                       epos += i;
+                       }
+                       buf[epos] = '\0';
+                       if ((flag & EFCR) != 0) {
+                               ttputc(CCHR('M'));
+                               ttflush();
+                       }
+                       if (macrodef) {
+                               struct line     *lp;
+
+                               if ((lp = lalloc(cpos)) == NULL)
+                                       goto memfail;
+                               lp->l_fp = maclcur->l_fp;
+                               maclcur->l_fp = lp;
+                               lp->l_bp = maclcur;
+                               maclcur = lp;
+                               bcopy(buf, lp->l_text, cpos);
+                       }
+                       ret = buf;
+                       goto done;
+               case CCHR('G'):                 /* bell, abort */
+                       eputc(CCHR('G'));
+                       (void)ctrlg(FFRAND, 0);
+                       ttflush();
+                       ret = NULL;
+                       goto done;
+               case CCHR('H'):                 /* rubout, erase */
+               case CCHR('?'):
+                       if (cpos != 0) {
+                               y = buf[--cpos];
+                               epos--;
+                               ttputc('\b');
+                               ttcol--;
+                               if (ISCTRL(y) != FALSE) {
+                                       ttputc('\b');
+                                       ttcol--;
+                               }
+                               rr = ttrow;
+                               cc = ttcol;
+                               for (i = cpos; i < epos; i++) {
+                                       buf[i] = buf[i + 1];
+                                       eputc(buf[i]);
+                               }
+                               ttputc(' ');
+                               if (ISCTRL(y) != FALSE) {
+                                       ttputc(' ');
+                                       ttputc('\b');
+                               }
+                               ttputc('\b');
+                               ttmove(rr, cc);
+                               ttflush();
+                       }
+                       break;
+               case CCHR('X'):                 /* kill line */
+               case CCHR('U'):
+                       while (cpos != 0) {
+                               ttputc('\b');
+                               ttputc(' ');
+                               ttputc('\b');
+                               --ttcol;
+                               if (ISCTRL(buf[--cpos]) != FALSE) {
+                                       ttputc('\b');
+                                       ttputc(' ');
+                                       ttputc('\b');
+                                       --ttcol;
+                               }
+                               epos--;
+                       }
+                       ttflush();
+                       break;
+               case CCHR('W'):                 /* kill to beginning of word */
+                       while ((cpos > 0) && !ISWORD(buf[cpos - 1])) {
+                               ttputc('\b');
+                               ttputc(' ');
+                               ttputc('\b');
+                               --ttcol;
+                               if (ISCTRL(buf[--cpos]) != FALSE) {
+                                       ttputc('\b');
+                                       ttputc(' ');
+                                       ttputc('\b');
+                                       --ttcol;
+                               }
+                               epos--;
+                       }
+                       while ((cpos > 0) && ISWORD(buf[cpos - 1])) {
+                               ttputc('\b');
+                               ttputc(' ');
+                               ttputc('\b');
+                               --ttcol;
+                               if (ISCTRL(buf[--cpos]) != FALSE) {
+                                       ttputc('\b');
+                                       ttputc(' ');
+                                       ttputc('\b');
+                                       --ttcol;
+                               }
+                               epos--;
+                       }
+                       ttflush();
+                       break;
+               case CCHR('\\'):
+               case CCHR('Q'):                 /* quote next */
+                       c = getkey(FALSE);
+                       /* FALLTHROUGH */
+               default:
+                       if (dynbuf && epos + 1 >= nbuf) {
+                               void *newp;
+                               size_t newsize = epos + epos + 16;
+                               if ((newp = realloc(buf, newsize)) == NULL)
+                                       goto memfail;
+                               buf = newp;
+                               nbuf = newsize;
+                       }
+                       if (!dynbuf && epos + 1 >= nbuf) {
+                               ewprintf("Line too long");
+                               return (emptyval);
+                       }
+                       for (i = epos; i > cpos; i--)
+                               buf[i] = buf[i - 1];
+                       buf[cpos++] = (char)c;
+                       epos++;
+                       eputc((char)c);
+                       cc = ttcol;
+                       rr = ttrow;
+                       for (i = cpos; i < epos; i++)
+                               eputc(buf[i]);
+                       ttmove(rr, cc);
+                       ttflush();
+               }
+       }
+done:
+       if (cwin == TRUE) {
+               /* blow away cpltion window */
+               bp = bfind("*Completions*", TRUE);
+               if ((wp = popbuf(bp, WEPHEM)) != NULL) {
+                       if (wp->w_flag & WEPHEM) {
+                               curwp = wp;
+                               delwind(FFRAND, 1);
+                       } else {
+                               killbuffer(bp);
+                       }
+               }
+       }
+       return (ret);
+memfail:
+       if (dynbuf && buf)
+               free(buf);
+       ewprintf("Out of memory");
+       return (emptyval);
+}
+
+/*
+ * Do completion on a list of objects.
+ * c is SPACE, TAB, or CR
+ * return TRUE if matched (or partially matched)
+ * FALSE is result is ambiguous,
+ * ABORT on error.
+ */
+static int
+complt(int flags, int c, char *buf, size_t nbuf, int cpos, int *nx)
+{
+       struct list     *lh, *lh2;
+       struct list     *wholelist = NULL;
+       int      i, nxtra, nhits, bxtra, msglen, nshown;
+       int      wflag = FALSE;
+       char    *msg;
+
+       lh = lh2 = NULL;
+
+       if ((flags & EFFUNC) != 0) {
+               buf[cpos] = '\0';
+               wholelist = lh = complete_function_list(buf);
+       } else if ((flags & EFBUF) != 0) {
+               lh = &(bheadp->b_list);
+       } else if ((flags & EFFILE) != 0) {
+               buf[cpos] = '\0';
+               wholelist = lh = make_file_list(buf);
+       } else
+               panic("broken complt call: flags");
+
+       if (c == ' ')
+               wflag = TRUE;
+       else if (c != '\t' && c != CCHR('M'))
+               panic("broken complt call: c");
+
+       nhits = 0;
+       nxtra = HUGE;
+
+       for (; lh != NULL; lh = lh->l_next) {
+               if (memcmp(buf, lh->l_name, cpos) != 0)
+                       continue;
+               if (nhits == 0)
+                       lh2 = lh;
+               ++nhits;
+               if (lh->l_name[cpos] == '\0')
+                       nxtra = -1; /* exact match */
+               else {
+                       bxtra = getxtra(lh, lh2, cpos, wflag);
+                       if (bxtra < nxtra)
+                               nxtra = bxtra;
+                       lh2 = lh;
+               }
+       }
+       if (nhits == 0)
+               msg = " [No match]";
+       else if (nhits > 1 && nxtra == 0)
+               msg = " [Ambiguous. Ctrl-G to cancel]";
+       else {
+               /*
+                * Being lazy - ought to check length, but all things
+                * autocompleted have known types/lengths.
+                */
+               if (nxtra < 0 && nhits > 1 && c == ' ')
+                       nxtra = 1; /* ??? */
+               for (i = 0; i < nxtra && cpos < nbuf; ++i) {
+                       buf[cpos] = lh2->l_name[cpos];
+                       eputc(buf[cpos++]);
+               }
+               /* XXX should grow nbuf */
+               ttflush();
+               free_file_list(wholelist);
+               *nx = nxtra;
+               if (nxtra < 0 && c != CCHR('M')) /* exact */
+                       *nx = 0;
+               return (TRUE);
+       }
+
+       /*
+        * wholelist is NULL if we are doing buffers.  Want to free lists
+        * that were created for us, but not the buffer list!
+        */
+       free_file_list(wholelist);
+
+       /* Set up backspaces, etc., being mindful of echo line limit. */
+       msglen = strlen(msg);
+       nshown = (ttcol + msglen + 2 > ncol) ?
+               ncol - ttcol - 2 : msglen;
+       eputs(msg);
+       ttcol -= (i = nshown);  /* update ttcol!                 */
+       while (i--)             /* move back before msg          */
+               ttputc('\b');
+       ttflush();              /* display to user               */
+       i = nshown;
+       while (i--)             /* blank out on next flush       */
+               eputc(' ');
+       ttcol -= (i = nshown);  /* update ttcol on BS's          */
+       while (i--)
+               ttputc('\b');   /* update ttcol again!           */
+       *nx = nxtra;
+       return ((nhits > 0) ? TRUE : FALSE);
+}
+
+/*
+ * Do completion on a list of objects, listing instead of completing.
+ */
+static int
+complt_list(int flags, char *buf, int cpos)
+{
+       struct list     *lh, *lh2, *lh3;
+       struct list     *wholelist = NULL;
+       struct buffer   *bp;
+       int      i, maxwidth, width;
+       int      preflen = 0;
+       int      oldrow = ttrow;
+       int      oldcol = ttcol;
+       int      oldhue = tthue;
+       char     *linebuf;
+       size_t   linesize, len;
+       char *cp;
+
+       lh = NULL;
+
+       ttflush();
+
+       /* The results are put into a completion buffer. */
+       bp = bfind("*Completions*", TRUE);
+       if (bclear(bp) == FALSE)
+               return (FALSE);
+
+       /*
+        * First get the list of objects.  This list may contain only
+        * the ones that complete what has been typed, or may be the
+        * whole list of all objects of this type.  They are filtered
+        * later in any case.  Set wholelist if the list has been
+        * cons'ed up just for us, so we can free it later.  We have
+        * to copy the buffer list for this function even though we
+        * didn't for complt.  The sorting code does destructive
+        * changes to the list, which we don't want to happen to the
+        * main buffer list!
+        */
+       if ((flags & EFBUF) != 0)
+               wholelist = lh = copy_list(&(bheadp->b_list));
+       else if ((flags & EFFUNC) != 0) {
+               buf[cpos] = '\0';
+               wholelist = lh = complete_function_list(buf);
+       } else if ((flags & EFFILE) != 0) {
+               buf[cpos] = '\0';
+               wholelist = lh = make_file_list(buf);
+               /*
+                * We don't want to display stuff up to the / for file
+                * names preflen is the list of a prefix of what the
+                * user typed that should not be displayed.
+                */
+               cp = strrchr(buf, '/');
+               if (cp)
+                       preflen = cp - buf + 1;
+       } else
+               panic("broken complt call: flags");
+
+       /*
+        * Sort the list, since users expect to see it in alphabetic
+        * order.
+        */
+       lh2 = lh;
+       while (lh2 != NULL) {
+               lh3 = lh2->l_next;
+               while (lh3 != NULL) {
+                       if (strcmp(lh2->l_name, lh3->l_name) > 0) {
+                               cp = lh2->l_name;
+                               lh2->l_name = lh3->l_name;
+                               lh3->l_name = cp;
+                       }
+                       lh3 = lh3->l_next;
+               }
+               lh2 = lh2->l_next;
+       }
+
+       /*
+        * First find max width of object to be displayed, so we can
+        * put several on a line.
+        */
+       maxwidth = 0;
+       lh2 = lh;
+       while (lh2 != NULL) {
+               for (i = 0; i < cpos; ++i) {
+                       if (buf[i] != lh2->l_name[i])
+                               break;
+               }
+               if (i == cpos) {
+                       width = strlen(lh2->l_name);
+                       if (width > maxwidth)
+                               maxwidth = width;
+               }
+               lh2 = lh2->l_next;
+       }
+       maxwidth += 1 - preflen;
+
+       /*
+        * Now do the display.  Objects are written into linebuf until
+        * it fills, and then put into the help buffer.
+        */
+       linesize = MAX(ncol, maxwidth) + 1;
+       if ((linebuf = malloc(linesize)) == NULL)
+               return (FALSE);
+       width = 0;
+
+       /*
+        * We're going to strlcat() into the buffer, so it has to be
+        * NUL terminated.
+        */
+       linebuf[0] = '\0';
+       for (lh2 = lh; lh2 != NULL; lh2 = lh2->l_next) {
+               for (i = 0; i < cpos; ++i) {
+                       if (buf[i] != lh2->l_name[i])
+                               break;
+               }
+               /* if we have a match */
+               if (i == cpos) {
+                       /* if it wraps */
+                       if ((width + maxwidth) > ncol) {
+                               addline(bp, linebuf);
+                               linebuf[0] = '\0';
+                               width = 0;
+                       }
+                       len = strlcat(linebuf, lh2->l_name + preflen,
+                           linesize);
+                       width += maxwidth;
+                       if (len < width && width < linesize) {
+                               /* pad so the objects nicely line up */
+                               memset(linebuf + len, ' ',
+                                   maxwidth - strlen(lh2->l_name + preflen));
+                               linebuf[width] = '\0';
+                       }
+               }
+       }
+       if (width > 0)
+               addline(bp, linebuf);
+       free(linebuf);
+
+       /*
+        * Note that we free lists only if they are put in wholelist lists
+        * that were built just for us should be freed.  However when we use
+        * the buffer list, obviously we don't want it freed.
+        */
+       free_file_list(wholelist);
+       popbuftop(bp, WEPHEM);  /* split the screen and put up the help
+                                * buffer */
+       update();               /* needed to make the new stuff actually
+                                * appear */
+       ttmove(oldrow, oldcol); /* update leaves cursor in arbitrary place */
+       ttcolor(oldhue);        /* with arbitrary color */
+       ttflush();
+       return (0);
+}
+
+/*
+ * The "lp1" and "lp2" point to list structures.  The "cpos" is a horizontal
+ * position in the name.  Return the longest block of characters that can be
+ * autocompleted at this point.  Sometimes the two symbols are the same, but
+ * this is normal.
+ */
+int
+getxtra(struct list *lp1, struct list *lp2, int cpos, int wflag)
+{
+       int     i;
+
+       i = cpos;
+       for (;;) {
+               if (lp1->l_name[i] != lp2->l_name[i])
+                       break;
+               if (lp1->l_name[i] == '\0')
+                       break;
+               ++i;
+               if (wflag && !ISWORD(lp1->l_name[i - 1]))
+                       break;
+       }
+       return (i - cpos);
+}
+
+/*
+ * Special "printf" for the echo line.  Each call to "ewprintf" starts a
+ * new line in the echo area, and ends with an erase to end of the echo
+ * line.  The formatting is done by a call to the standard formatting
+ * routine.
+ */
+/* VARARGS */
+void
+ewprintf(const char *fmt, ...)
+{
+       va_list  ap;
+
+       if (inmacro)
+               return;
+
+       va_start(ap, fmt);
+       ttcolor(CTEXT);
+       ttmove(nrow - 1, 0);
+       eformat(fmt, ap);
+       va_end(ap);
+       tteeol();
+       ttflush();
+       epresf = TRUE;
+}
+
+/*
+ * Printf style formatting. This is called by both "ewprintf" and "ereply"
+ * to provide formatting services to their clients.  The move to the start
+ * of the echo line, and the erase to the end of the echo line, is done by
+ * the caller. 
+ * %c prints the "name" of the supplied character.
+ * %k prints the name of the current key (and takes no arguments).
+ * %d prints a decimal integer
+ * %o prints an octal integer
+ * %p prints a pointer
+ * %s prints a string
+ * %ld prints a long word
+ * Anything else is echoed verbatim
+ */
+static void
+eformat(const char *fp, va_list ap)
+{
+       char    kname[NKNAME], tmp[100], *cp;
+       int     c;
+
+       while ((c = *fp++) != '\0') {
+               if (c != '%')
+                       eputc(c);
+               else {
+                       c = *fp++;
+                       switch (c) {
+                       case 'c':
+                               getkeyname(kname, sizeof(kname),
+                                   va_arg(ap, int));
+                               eputs(kname);
+                               break;
+
+                       case 'k':
+                               for (cp = kname, c = 0; c < key.k_count; c++) {
+                                       if (c)
+                                               *cp++ = ' ';
+                                       cp = getkeyname(cp, sizeof(kname) -
+                                           (cp - kname) - 1, key.k_chars[c]);
+                               }
+                               eputs(kname);
+                               break;
+
+                       case 'd':
+                               eputi(va_arg(ap, int), 10);
+                               break;
+
+                       case 'o':
+                               eputi(va_arg(ap, int), 8);
+                               break;
+
+                       case 'p':
+                               snprintf(tmp, sizeof(tmp), "%p",
+                                   va_arg(ap, void *));
+                               eputs(tmp);
+                               break;
+
+                       case 's':
+                               eputs(va_arg(ap, char *));
+                               break;
+
+                       case 'l':
+                               /* explicit longword */
+                               c = *fp++;
+                               switch (c) {
+                               case 'd':
+                                       eputl(va_arg(ap, long), 10);
+                                       break;
+                               default:
+                                       eputc(c);
+                                       break;
+                               }
+                               break;
+
+                       default:
+                               eputc(c);
+                       }
+               }
+       }
+}
+
+/*
+ * Put integer, in radix "r".
+ */
+static void
+eputi(int i, int r)
+{
+       int      q;
+
+       if (i < 0) {
+               eputc('-');
+               i = -i;
+       }
+       if ((q = i / r) != 0)
+               eputi(q, r);
+       eputc(i % r + '0');
+}
+
+/*
+ * Put long, in radix "r".
+ */
+static void
+eputl(long l, int r)
+{
+       long     q;
+
+       if (l < 0) {
+               eputc('-');
+               l = -l;
+       }
+       if ((q = l / r) != 0)
+               eputl(q, r);
+       eputc((int)(l % r) + '0');
+}
+
+/*
+ * Put string.
+ */
+static void
+eputs(const char *s)
+{
+       int      c;
+
+       while ((c = *s++) != '\0')
+               eputc(c);
+}
+
+/*
+ * Put character.  Watch for control characters, and for the line getting
+ * too long.
+ */
+static void
+eputc(char c)
+{
+       if (ttcol + 2 < ncol) {
+               if (ISCTRL(c)) {
+                       eputc('^');
+                       c = CCHR(c);
+               }
+               ttputc(c);
+               ++ttcol;
+       }
+}
+
+void
+free_file_list(struct list *lp)
+{
+       struct list     *next;
+
+       while (lp) {
+               next = lp->l_next;
+               free(lp->l_name);
+               free(lp);
+               lp = next;
+       }
+}
+
+static struct list *
+copy_list(struct list *lp)
+{
+       struct list     *current, *last, *nxt;
+
+       last = NULL;
+       while (lp) {
+               current = malloc(sizeof(struct list));
+               if (current == NULL) {
+                       /* Free what we have allocated so far */
+                       for (current = last; current; current = nxt) {
+                               nxt = current->l_next;
+                               free(current->l_name);
+                               free(current);
+                       }
+                       return (NULL);
+               }
+               current->l_next = last;
+               current->l_name = strdup(lp->l_name);
+               last = current;
+               lp = lp->l_next;
+       }
+       return (last);
+}
diff --git a/mg/extend.c b/mg/extend.c
new file mode 100644 (file)
index 0000000..2f47de6
--- /dev/null
@@ -0,0 +1,963 @@
+/*     $OpenBSD: extend.c,v 1.53 2012/05/25 04:56:58 lum Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ *     Extended (M-X) commands, rebinding, and startup file processing.
+ */
+#include "chrdef.h"
+#include "def.h"
+#include "kbd.h"
+#include "funmap.h"
+
+#include <sys/types.h>
+#include <ctype.h>
+
+#include "macro.h"
+
+#ifdef FKEYS
+#include "key.h"
+#ifndef        BINDKEY
+#define        BINDKEY                 /* bindkey is used by FKEYS startup code */
+#endif /* !BINDKEY */
+#endif /* FKEYS */
+
+#include <ctype.h>
+
+static int      remap(KEYMAP *, int, PF, KEYMAP *);
+static KEYMAP  *reallocmap(KEYMAP *);
+static void     fixmap(KEYMAP *, KEYMAP *, KEYMAP *);
+static int      dobind(KEYMAP *, const char *, int);
+static char    *skipwhite(char *);
+static char    *parsetoken(char *);
+#ifdef BINDKEY
+static int      bindkey(KEYMAP **, const char *, KCHAR *, int);
+#endif /* BINDKEY */
+
+/*
+ * Insert a string, mainly for use from macros (created by selfinsert).
+ */
+/* ARGSUSED */
+int
+insert(int f, int n)
+{
+       char     buf[128], *bufp, *cp;
+       int      count, c;
+
+       if (inmacro) {
+               while (--n >= 0) {
+                       for (count = 0; count < maclcur->l_used; count++) {
+                               if ((((c = maclcur->l_text[count]) == '\n')
+                                   ? lnewline() : linsert(1, c)) != TRUE)
+                                       return (FALSE);
+                       }
+               }
+               maclcur = maclcur->l_fp;
+               return (TRUE);
+       }
+       if (n == 1)
+               /* CFINS means selfinsert can tack on the end */
+               thisflag |= CFINS;
+
+       if ((bufp = eread("Insert: ", buf, sizeof(buf), EFNEW)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       while (--n >= 0) {
+               cp = buf;
+               while (*cp) {
+                       if (((*cp == '\n') ? lnewline() : linsert(1, *cp))
+                           != TRUE)
+                               return (FALSE);
+                       cp++;
+               }
+       }
+       return (TRUE);
+}
+
+/*
+ * Bind a key to a function.  Cases range from the trivial (replacing an
+ * existing binding) to the extremely complex (creating a new prefix in a
+ * map_element that already has one, so the map_element must be split,
+ * but the keymap doesn't have enough room for another map_element, so
+ * the keymap is reallocated). No attempt is made to reclaim space no
+ * longer used, if this is a problem flags must be added to indicate
+ * malloced versus static storage in both keymaps and map_elements.
+ * Structure assignments would come in real handy, but K&R based compilers
+ * don't have them.  Care is taken so running out of memory will leave
+ * the keymap in a usable state.
+ * Parameters are:
+ * curmap:     pointer to the map being changed
+ * c:          character being changed
+ * funct:      function being changed to
+ * pref_map:   if funct==NULL, map to bind to or NULL for new
+ */
+static int
+remap(KEYMAP *curmap, int c, PF funct, KEYMAP *pref_map)
+{
+       int              i, n1, n2, nold;
+       KEYMAP          *mp, *newmap;
+       PF              *pfp;
+       struct map_element      *mep;
+
+       if (ele >= &curmap->map_element[curmap->map_num] || c < ele->k_base) {
+               if (ele > &curmap->map_element[0] && (funct != NULL ||
+                   (ele - 1)->k_prefmap == NULL))
+                       n1 = c - (ele - 1)->k_num;
+               else
+                       n1 = HUGE;
+               if (ele < &curmap->map_element[curmap->map_num] &&
+                   (funct != NULL || ele->k_prefmap == NULL))
+                       n2 = ele->k_base - c;
+               else
+                       n2 = HUGE;
+               if (n1 <= MAPELEDEF && n1 <= n2) {
+                       ele--;
+                       if ((pfp = calloc(c - ele->k_base + 1,
+                           sizeof(PF))) == NULL) {
+                               ewprintf("Out of memory");
+                               return (FALSE);
+                       }
+                       nold = ele->k_num - ele->k_base + 1;
+                       for (i = 0; i < nold; i++)
+                               pfp[i] = ele->k_funcp[i];
+                       while (--n1)
+                               pfp[i++] = curmap->map_default;
+                       pfp[i] = funct;
+                       ele->k_num = c;
+                       ele->k_funcp = pfp;
+               } else if (n2 <= MAPELEDEF) {
+                       if ((pfp = calloc(ele->k_num - c + 1,
+                           sizeof(PF))) == NULL) {
+                               ewprintf("Out of memory");
+                               return (FALSE);
+                       }
+                       nold = ele->k_num - ele->k_base + 1;
+                       for (i = 0; i < nold; i++)
+                               pfp[i + n2] = ele->k_funcp[i];
+                       while (--n2)
+                               pfp[n2] = curmap->map_default;
+                       pfp[0] = funct;
+                       ele->k_base = c;
+                       ele->k_funcp = pfp;
+               } else {
+                       if (curmap->map_num >= curmap->map_max) {
+                               if ((newmap = reallocmap(curmap)) == NULL)
+                                       return (FALSE);
+                               curmap = newmap;
+                       }
+                       if ((pfp = malloc(sizeof(PF))) == NULL) {
+                               ewprintf("Out of memory");
+                               return (FALSE);
+                       }
+                       pfp[0] = funct;
+                       for (mep = &curmap->map_element[curmap->map_num];
+                           mep > ele; mep--) {
+                               mep->k_base = (mep - 1)->k_base;
+                               mep->k_num = (mep - 1)->k_num;
+                               mep->k_funcp = (mep - 1)->k_funcp;
+                               mep->k_prefmap = (mep - 1)->k_prefmap;
+                       }
+                       ele->k_base = c;
+                       ele->k_num = c;
+                       ele->k_funcp = pfp;
+                       ele->k_prefmap = NULL;
+                       curmap->map_num++;
+               }
+               if (funct == NULL) {
+                       if (pref_map != NULL)
+                               ele->k_prefmap = pref_map;
+                       else {
+                               if ((mp = malloc(sizeof(KEYMAP) +
+                                   (MAPINIT - 1) * sizeof(struct map_element))) == NULL) {
+                                       ewprintf("Out of memory");
+                                       ele->k_funcp[c - ele->k_base] =
+                                           curmap->map_default;
+                                       return (FALSE);
+                               }
+                               mp->map_num = 0;
+                               mp->map_max = MAPINIT;
+                               mp->map_default = rescan;
+                               ele->k_prefmap = mp;
+                       }
+               }
+       } else {
+               n1 = c - ele->k_base;
+               if (ele->k_funcp[n1] == funct && (funct != NULL ||
+                   pref_map == NULL || pref_map == ele->k_prefmap))
+                       /* no change */
+                       return (TRUE);
+               if (funct != NULL || ele->k_prefmap == NULL) {
+                       if (ele->k_funcp[n1] == NULL)
+                               ele->k_prefmap = NULL;
+                       /* easy case */
+                       ele->k_funcp[n1] = funct;
+                       if (funct == NULL) {
+                               if (pref_map != NULL)
+                                       ele->k_prefmap = pref_map;
+                               else {
+                                       if ((mp = malloc(sizeof(KEYMAP) +
+                                           (MAPINIT - 1) *
+                                           sizeof(struct map_element))) == NULL) {
+                                               ewprintf("Out of memory");
+                                               ele->k_funcp[c - ele->k_base] =
+                                                   curmap->map_default;
+                                               return (FALSE);
+                                       }
+                                       mp->map_num = 0;
+                                       mp->map_max = MAPINIT;
+                                       mp->map_default = rescan;
+                                       ele->k_prefmap = mp;
+                               }
+                       }
+               } else {
+                       /*
+                        * This case is the splits.
+                        * Determine which side of the break c goes on
+                        * 0 = after break; 1 = before break
+                        */
+                       n2 = 1;
+                       for (i = 0; n2 && i < n1; i++)
+                               n2 &= ele->k_funcp[i] != NULL;
+                       if (curmap->map_num >= curmap->map_max) {
+                               if ((newmap = reallocmap(curmap)) == NULL)
+                                       return (FALSE);
+                               curmap = newmap;
+                       }
+                       if ((pfp = calloc(ele->k_num - c + !n2,
+                           sizeof(PF))) == NULL) {
+                               ewprintf("Out of memory");
+                               return (FALSE);
+                       }
+                       ele->k_funcp[n1] = NULL;
+                       for (i = n1 + n2; i <= ele->k_num - ele->k_base; i++)
+                               pfp[i - n1 - n2] = ele->k_funcp[i];
+                       for (mep = &curmap->map_element[curmap->map_num];
+                           mep > ele; mep--) {
+                               mep->k_base = (mep - 1)->k_base;
+                               mep->k_num = (mep - 1)->k_num;
+                               mep->k_funcp = (mep - 1)->k_funcp;
+                               mep->k_prefmap = (mep - 1)->k_prefmap;
+                       }
+                       ele->k_num = c - !n2;
+                       (ele + 1)->k_base = c + n2;
+                       (ele + 1)->k_funcp = pfp;
+                       ele += !n2;
+                       ele->k_prefmap = NULL;
+                       curmap->map_num++;
+                       if (pref_map == NULL) {
+                               if ((mp = malloc(sizeof(KEYMAP) + (MAPINIT - 1)
+                                   * sizeof(struct map_element))) == NULL) {
+                                       ewprintf("Out of memory");
+                                       ele->k_funcp[c - ele->k_base] =
+                                           curmap->map_default;
+                                       return (FALSE);
+                               }
+                               mp->map_num = 0;
+                               mp->map_max = MAPINIT;
+                               mp->map_default = rescan;
+                               ele->k_prefmap = mp;
+                       } else
+                               ele->k_prefmap = pref_map;
+               }
+       }
+       return (TRUE);
+}
+
+/*
+ * Reallocate a keymap. Returns NULL (without trashing the current map)
+ * on failure.
+ */
+static KEYMAP *
+reallocmap(KEYMAP *curmap)
+{
+       struct maps_s   *mps;
+       KEYMAP  *mp;
+       int      i;
+
+       if (curmap->map_max > SHRT_MAX - MAPGROW) {
+               ewprintf("keymap too large");
+               return (NULL);
+       }
+       if ((mp = malloc(sizeof(KEYMAP) + (curmap->map_max + (MAPGROW - 1)) *
+           sizeof(struct map_element))) == NULL) {
+               ewprintf("Out of memory");
+               return (NULL);
+       }
+       mp->map_num = curmap->map_num;
+       mp->map_max = curmap->map_max + MAPGROW;
+       mp->map_default = curmap->map_default;
+       for (i = curmap->map_num; i--;) {
+               mp->map_element[i].k_base = curmap->map_element[i].k_base;
+               mp->map_element[i].k_num = curmap->map_element[i].k_num;
+               mp->map_element[i].k_funcp = curmap->map_element[i].k_funcp;
+               mp->map_element[i].k_prefmap = curmap->map_element[i].k_prefmap;
+       }
+       for (mps = maps; mps != NULL; mps = mps->p_next) {
+               if (mps->p_map == curmap)
+                       mps->p_map = mp;
+               else
+                       fixmap(curmap, mp, mps->p_map);
+       }
+       ele = &mp->map_element[ele - &curmap->map_element[0]];
+       return (mp);
+}
+
+/*
+ * Fix references to a reallocated keymap (recursive).
+ */
+static void
+fixmap(KEYMAP *curmap, KEYMAP *mp, KEYMAP *mt)
+{
+       int      i;
+
+       for (i = mt->map_num; i--;) {
+               if (mt->map_element[i].k_prefmap != NULL) {
+                       if (mt->map_element[i].k_prefmap == curmap)
+                               mt->map_element[i].k_prefmap = mp;
+                       else
+                               fixmap(curmap, mp, mt->map_element[i].k_prefmap);
+               }
+       }
+}
+
+/*
+ * Do the input for local-set-key, global-set-key  and define-key
+ * then call remap to do the work.
+ */
+static int
+dobind(KEYMAP *curmap, const char *p, int unbind)
+{
+       KEYMAP  *pref_map = NULL;
+       PF       funct;
+       char     bprompt[80], *bufp, *pep;
+       int      c, s, n;
+
+       if (macrodef) {
+               /*
+                * Keystrokes aren't collected. Not hard, but pretty useless.
+                * Would not work for function keys in any case.
+                */
+               ewprintf("Can't rebind key in macro");
+               return (FALSE);
+       }
+       if (inmacro) {
+               for (s = 0; s < maclcur->l_used - 1; s++) {
+                       if (doscan(curmap, c = CHARMASK(maclcur->l_text[s]), &curmap)
+                           != NULL) {
+                               if (remap(curmap, c, NULL, NULL)
+                                   != TRUE)
+                                       return (FALSE);
+                       }
+               }
+               (void)doscan(curmap, c = maclcur->l_text[s], NULL);
+               maclcur = maclcur->l_fp;
+       } else {
+               n = strlcpy(bprompt, p, sizeof(bprompt));
+               if (n >= sizeof(bprompt))
+                       n = sizeof(bprompt) - 1;
+               pep = bprompt + n;
+               for (;;) {
+                       ewprintf("%s", bprompt);
+                       pep[-1] = ' ';
+                       pep = getkeyname(pep, sizeof(bprompt) -
+                           (pep - bprompt), c = getkey(FALSE));
+                       if (doscan(curmap, c, &curmap) != NULL)
+                               break;
+                       *pep++ = '-';
+                       *pep = '\0';
+               }
+       }
+       if (unbind)
+               funct = rescan;
+       else {
+               if ((bufp = eread("%s to command: ", bprompt, sizeof(bprompt),
+                   EFFUNC | EFNEW, bprompt)) == NULL)
+                       return (ABORT);
+               else if (bufp[0] == '\0')
+                       return (FALSE);
+               if (((funct = name_function(bprompt)) == NULL) ?
+                   (pref_map = name_map(bprompt)) == NULL : funct == NULL) {
+                       ewprintf("[No match]");
+                       return (FALSE);
+               }
+       }
+       return (remap(curmap, c, funct, pref_map));
+}
+
+/*
+ * bindkey: bind key sequence to a function in the specified map.  Used by
+ * excline so it can bind function keys.  To close to release to change
+ * calling sequence, should just pass KEYMAP *curmap rather than
+ * KEYMAP **mapp.
+ */
+#ifdef BINDKEY
+static int
+bindkey(KEYMAP **mapp, const char *fname, KCHAR *keys, int kcount)
+{
+       KEYMAP  *curmap = *mapp;
+       KEYMAP  *pref_map = NULL;
+       PF       funct;
+       int      c;
+
+       if (fname == NULL)
+               funct = rescan;
+       else if (((funct = name_function(fname)) == NULL) ?
+           (pref_map = name_map(fname)) == NULL : funct == NULL) {
+               ewprintf("[No match: %s]", fname);
+               return (FALSE);
+       }
+       while (--kcount) {
+               if (doscan(curmap, c = *keys++, &curmap) != NULL) {
+                       if (remap(curmap, c, NULL, NULL) != TRUE)
+                               return (FALSE);
+                       /*
+                        * XXX - Bizzarreness. remap creates an empty KEYMAP
+                        *       that the last key is supposed to point to.
+                        */
+                       curmap = ele->k_prefmap;
+               }
+       }
+       (void)doscan(curmap, c = *keys, NULL);
+       return (remap(curmap, c, funct, pref_map));
+}
+
+#ifdef FKEYS
+/*
+ * Wrapper for bindkey() that converts escapes.
+ */
+int
+dobindkey(KEYMAP *map, const char *func, const char *str)
+{
+       int      i;
+
+       for (i = 0; *str && i < MAXKEY; i++) {
+               /* XXX - convert numbers w/ strol()? */
+               if (*str == '^' && *(str + 1) !=  '\0') {
+                       key.k_chars[i] = CCHR(toupper(*++str));
+               } else if (*str == '\\' && *(str + 1) != '\0') {
+                       switch (*++str) {
+                       case '^':
+                               key.k_chars[i] = '^';
+                               break;
+                       case 't':
+                       case 'T':
+                               key.k_chars[i] = '\t';
+                               break;
+                       case 'n':
+                       case 'N':
+                               key.k_chars[i] = '\n';
+                               break;
+                       case 'r':
+                       case 'R':
+                               key.k_chars[i] = '\r';
+                               break;
+                       case 'e':
+                       case 'E':
+                               key.k_chars[i] = CCHR('[');
+                               break;
+                       case '\\':
+                               key.k_chars[i] = '\\';
+                               break;
+                       }
+               } else
+                       key.k_chars[i] = *str;
+               str++;
+       }
+       key.k_count = i;
+       return (bindkey(&map, func, key.k_chars, key.k_count));
+}
+#endif /* FKEYS */
+#endif /* BINDKEY */
+
+/*
+ * This function modifies the fundamental keyboard map.
+ */
+/* ARGSUSED */
+int
+bindtokey(int f, int n)
+{
+       return (dobind(fundamental_map, "Global set key: ", FALSE));
+}
+
+/*
+ * This function modifies the current mode's keyboard map.
+ */
+/* ARGSUSED */
+int
+localbind(int f, int n)
+{
+       return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map,
+           "Local set key: ", FALSE));
+}
+
+/*
+ * This function redefines a key in any keymap.
+ */
+/* ARGSUSED */
+int
+redefine_key(int f, int n)
+{
+       static char      buf[48];
+       char             tmp[32], *bufp;
+       KEYMAP          *mp;
+
+       (void)strlcpy(buf, "Define key map: ", sizeof(buf));
+       if ((bufp = eread(buf, tmp, sizeof(tmp), EFNEW)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       (void)strlcat(buf, tmp, sizeof(buf));
+       if ((mp = name_map(tmp)) == NULL) {
+               ewprintf("Unknown map %s", tmp);
+               return (FALSE);
+       }
+       if (strlcat(buf, "key: ", sizeof(buf)) >= sizeof(buf))
+               return (FALSE);
+
+       return (dobind(mp, buf, FALSE));
+}
+
+/* ARGSUSED */
+int
+unbindtokey(int f, int n)
+{
+       return (dobind(fundamental_map, "Global unset key: ", TRUE));
+}
+
+/* ARGSUSED */
+int
+localunbind(int f, int n)
+{
+       return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map,
+           "Local unset key: ", TRUE));
+}
+
+/*
+ * Extended command. Call the message line routine to read in the command
+ * name and apply autocompletion to it. When it comes back, look the name
+ * up in the symbol table and run the command if it is found.  Print an
+ * error if there is anything wrong.
+ */
+int
+extend(int f, int n)
+{
+       PF       funct;
+       char     xname[NXNAME], *bufp;
+
+       if (!(f & FFARG))
+               bufp = eread("M-x ", xname, NXNAME, EFNEW | EFFUNC);
+       else
+               bufp = eread("%d M-x ", xname, NXNAME, EFNEW | EFFUNC, n);
+       if (bufp == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       if ((funct = name_function(bufp)) != NULL) {
+               if (macrodef) {
+                       struct line     *lp = maclcur;
+                       macro[macrocount - 1].m_funct = funct;
+                       maclcur = lp->l_bp;
+                       maclcur->l_fp = lp->l_fp;
+                       free(lp);
+               }
+               return ((*funct)(f, n));
+       }
+       ewprintf("[No match]");
+       return (FALSE);
+}
+
+/*
+ * Define the commands needed to do startup-file processing.
+ * This code is mostly a kludge just so we can get startup-file processing.
+ *
+ * If you're serious about having this code, you should rewrite it.
+ * To wit:
+ *     It has lots of funny things in it to make the startup-file look
+ *     like a GNU startup file; mostly dealing with parens and semicolons.
+ *     This should all vanish.
+ *
+ * We define eval-expression because it's easy.         It can make
+ * *-set-key or define-key set an arbitrary key sequence, so it isn't
+ * useless.
+ */
+
+/*
+ * evalexpr - get one line from the user, and run it.
+ */
+/* ARGSUSED */
+int
+evalexpr(int f, int n)
+{
+       char     exbuf[128], *bufp;
+
+       if ((bufp = eread("Eval: ", exbuf, sizeof(exbuf),
+           EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       return (excline(exbuf));
+}
+
+/*
+ * evalbuffer - evaluate the current buffer as line commands. Useful for
+ * testing startup files.
+ */
+/* ARGSUSED */
+int
+evalbuffer(int f, int n)
+{
+       struct line             *lp;
+       struct buffer           *bp = curbp;
+       int              s;
+       static char      excbuf[128];
+
+       for (lp = bfirstlp(bp); lp != bp->b_headp; lp = lforw(lp)) {
+               if (llength(lp) >= 128)
+                       return (FALSE);
+               (void)strncpy(excbuf, ltext(lp), llength(lp));
+
+               /* make sure it's terminated */
+               excbuf[llength(lp)] = '\0';
+               if ((s = excline(excbuf)) != TRUE)
+                       return (s);
+       }
+       return (TRUE);
+}
+
+/*
+ * evalfile - go get a file and evaluate it as line commands. You can
+ *     go get your own startup file if need be.
+ */
+/* ARGSUSED */
+int
+evalfile(int f, int n)
+{
+       char     fname[NFILEN], *bufp;
+
+       if ((bufp = eread("Load file: ", fname, NFILEN,
+           EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       return (load(fname));
+}
+
+/*
+ * load - go load the file name we got passed.
+ */
+int
+load(const char *fname)
+{
+       int      s = TRUE, line;
+       int      nbytes = 0;
+       char     excbuf[128];
+       FILE    *ffp;
+
+       if ((fname = adjustname(fname, TRUE)) == NULL)
+               /* just to be careful */
+               return (FALSE);
+
+       if (ffropen(&ffp, fname, NULL) != FIOSUC)
+               return (FALSE);
+
+       line = 0;
+       while ((s = ffgetline(ffp, excbuf, sizeof(excbuf) - 1, &nbytes))
+           == FIOSUC) {
+               line++;
+               excbuf[nbytes] = '\0';
+               if (excline(excbuf) != TRUE) {
+                       s = FIOERR;
+                       ewprintf("Error loading file %s at line %d", fname, line);
+                       break;
+               }
+       }
+       (void)ffclose(ffp, NULL);
+       excbuf[nbytes] = '\0';
+       if (s != FIOEOF || (nbytes && excline(excbuf) != TRUE))
+               return (FALSE);
+       return (TRUE);
+}
+
+/*
+ * excline - run a line from a load file or eval-expression.  If FKEYS is
+ * defined, duplicate functionality of dobind so function key values don't
+ * have to fit in type char.
+ */
+int
+excline(char *line)
+{
+       PF       fp;
+       struct line     *lp, *np;
+       int      status, c, f, n;
+       char    *funcp, *tmp;
+       char    *argp = NULL;
+       long     nl;
+#ifdef FKEYS
+       int      bind;
+       KEYMAP  *curmap;
+#define BINDARG                0  /* this arg is key to bind (local/global set key) */
+#define        BINDNO          1  /* not binding or non-quoted BINDARG */
+#define BINDNEXT       2  /* next arg " (define-key) */
+#define BINDDO         3  /* already found key to bind */
+#define BINDEXT                1  /* space for trailing \0 */
+#else /* FKEYS */
+#define BINDEXT                0
+#endif /* FKEYS */
+
+       lp = NULL;
+
+       if (macrodef || inmacro) {
+               ewprintf("Not now!");
+               return (FALSE);
+       }
+       f = 0;
+       n = 1;
+       funcp = skipwhite(line);
+       if (*funcp == '\0')
+               return (TRUE);  /* No error on blank lines */
+       line = parsetoken(funcp);
+       if (*line != '\0') {
+               *line++ = '\0';
+               line = skipwhite(line);
+               if (ISDIGIT(*line) || *line == '-') {
+                       argp = line;
+                       line = parsetoken(line);
+               }
+       }
+       if (argp != NULL) {
+               f = FFARG;
+               nl = strtol(argp, &tmp, 10);
+               if (*tmp != '\0')
+                       return (FALSE);
+               if (nl >= INT_MAX || nl <= INT_MIN)
+                       return (FALSE);
+               n = (int)nl;
+       }
+       if ((fp = name_function(funcp)) == NULL) {
+               ewprintf("Unknown function: %s", funcp);
+               return (FALSE);
+       }
+#ifdef FKEYS
+       if (fp == bindtokey || fp == unbindtokey) {
+               bind = BINDARG;
+               curmap = fundamental_map;
+       } else if (fp == localbind || fp == localunbind) {
+               bind = BINDARG;
+               curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
+       } else if (fp == redefine_key)
+               bind = BINDNEXT;
+       else
+               bind = BINDNO;
+#endif /* FKEYS */
+       /* Pack away all the args now... */
+       if ((np = lalloc(0)) == FALSE)
+               return (FALSE);
+       np->l_fp = np->l_bp = maclcur = np;
+       while (*line != '\0') {
+               argp = skipwhite(line);
+               if (*argp == '\0')
+                       break;
+               line = parsetoken(argp);
+               if (*argp != '"') {
+                       if (*argp == '\'')
+                               ++argp;
+                       if ((lp = lalloc((int) (line - argp) + BINDEXT)) ==
+                           NULL) {
+                               status = FALSE;
+                               goto cleanup;
+                       }
+                       bcopy(argp, ltext(lp), (int)(line - argp));
+#ifdef FKEYS
+                       /* don't count BINDEXT */
+                       lp->l_used--;
+                       if (bind == BINDARG)
+                               bind = BINDNO;
+#endif /* FKEYS */
+               } else {
+                       /* quoted strings are special */
+                       ++argp;
+#ifdef FKEYS
+                       if (bind != BINDARG) {
+#endif /* FKEYS */
+                               lp = lalloc((int)(line - argp) + BINDEXT);
+                               if (lp == NULL) {
+                                       status = FALSE;
+                                       goto cleanup;
+                               }
+                               lp->l_used = 0;
+#ifdef FKEYS
+                       } else
+                               key.k_count = 0;
+#endif /* FKEYS */
+                       while (*argp != '"' && *argp != '\0') {
+                               if (*argp != '\\')
+                                       c = *argp++;
+                               else {
+                                       switch (*++argp) {
+                                       case 't':
+                                       case 'T':
+                                               c = CCHR('I');
+                                               break;
+                                       case 'n':
+                                       case 'N':
+                                               c = CCHR('J');
+                                               break;
+                                       case 'r':
+                                       case 'R':
+                                               c = CCHR('M');
+                                               break;
+                                       case 'e':
+                                       case 'E':
+                                               c = CCHR('[');
+                                               break;
+                                       case '^':
+                                               /*
+                                                * split into two statements
+                                                * due to bug in OSK cpp
+                                                */
+                                               c = CHARMASK(*++argp);
+                                               c = ISLOWER(c) ?
+                                                   CCHR(TOUPPER(c)) : CCHR(c);
+                                               break;
+                                       case '0':
+                                       case '1':
+                                       case '2':
+                                       case '3':
+                                       case '4':
+                                       case '5':
+                                       case '6':
+                                       case '7':
+                                               c = *argp - '0';
+                                               if (argp[1] <= '7' &&
+                                                   argp[1] >= '0') {
+                                                       c <<= 3;
+                                                       c += *++argp - '0';
+                                                       if (argp[1] <= '7' &&
+                                                           argp[1] >= '0') {
+                                                               c <<= 3;
+                                                               c += *++argp
+                                                                   - '0';
+                                                       }
+                                               }
+                                               break;
+#ifdef FKEYS
+                                       case 'f':
+                                       case 'F':
+                                               c = *++argp - '0';
+                                               if (ISDIGIT(argp[1])) {
+                                                       c *= 10;
+                                                       c += *++argp - '0';
+                                               }
+                                               c += KFIRST;
+                                               break;
+#endif /* FKEYS */
+                                       default:
+                                               c = CHARMASK(*argp);
+                                               break;
+                                       }
+                                       argp++;
+                               }
+#ifdef FKEYS
+                               if (bind == BINDARG)
+                                       key.k_chars[key.k_count++] = c;
+                               else
+#endif /* FKEYS */
+                                       lp->l_text[lp->l_used++] = c;
+                       }
+                       if (*line)
+                               line++;
+               }
+#ifdef FKEYS
+               switch (bind) {
+               case BINDARG:
+                       bind = BINDDO;
+                       break;
+               case BINDNEXT:
+                       lp->l_text[lp->l_used] = '\0';
+                       if ((curmap = name_map(lp->l_text)) == NULL) {
+                               ewprintf("No such mode: %s", lp->l_text);
+                               status = FALSE;
+                               free(lp);
+                               goto cleanup;
+                       }
+                       free(lp);
+                       bind = BINDARG;
+                       break;
+               default:
+#endif /* FKEYS */
+                       lp->l_fp = np->l_fp;
+                       lp->l_bp = np;
+                       np->l_fp = lp;
+                       np = lp;
+#ifdef FKEYS
+               }
+#endif /* FKEYS */
+       }
+#ifdef FKEYS
+       switch (bind) {
+       default:
+               ewprintf("Bad args to set key");
+               status = FALSE;
+               break;
+       case BINDDO:
+               if (fp != unbindtokey && fp != localunbind) {
+                       lp->l_text[lp->l_used] = '\0';
+                       status = bindkey(&curmap, lp->l_text, key.k_chars,
+                           key.k_count);
+               } else
+                       status = bindkey(&curmap, NULL, key.k_chars,
+                           key.k_count);
+               break;
+       case BINDNO:
+#endif /* FKEYS */
+               inmacro = TRUE;
+               maclcur = maclcur->l_fp;
+               status = (*fp)(f, n);
+               inmacro = FALSE;
+#ifdef FKEYS
+       }
+#endif /* FKEYS */
+cleanup:
+       lp = maclcur->l_fp;
+       while (lp != maclcur) {
+               np = lp->l_fp;
+               free(lp);
+               lp = np;
+       }
+       free(lp);
+       return (status);
+}
+
+/*
+ * a pair of utility functions for the above
+ */
+static char *
+skipwhite(char *s)
+{
+       while (*s == ' ' || *s == '\t' || *s == ')' || *s == '(')
+               s++;
+       if (*s == ';')
+               *s = '\0';
+       return (s);
+}
+
+static char *
+parsetoken(char *s)
+{
+       if (*s != '"') {
+               while (*s && *s != ' ' && *s != '\t' && *s != ')' && *s != '(')
+                       s++;
+               if (*s == ';')
+                       *s = '\0';
+       } else
+               do {
+                       /*
+                        * Strings get special treatment.
+                        * Beware: You can \ out the end of the string!
+                        */
+                       if (*s == '\\')
+                               ++s;
+               } while (*++s != '"' && *s != '\0');
+       return (s);
+}
diff --git a/mg/file.c b/mg/file.c
new file mode 100644 (file)
index 0000000..acf64c1
--- /dev/null
+++ b/mg/file.c
@@ -0,0 +1,715 @@
+/*     $OpenBSD: file.c,v 1.81 2012/06/18 09:19:21 lum Exp $   */
+
+/* This file is in the public domain. */
+
+/*
+ *     File commands.
+ */
+
+#include "def.h"
+
+#include <sys/stat.h>
+
+#include <libgen.h>
+
+size_t xdirname(char *, const char *, size_t);
+
+/*
+ * Insert a file into the current buffer.  Real easy - just call the
+ * insertfile routine with the file name.
+ */
+/* ARGSUSED */
+int
+fileinsert(int f, int n)
+{
+       char     fname[NFILEN], *bufp, *adjf;
+
+       if (getbufcwd(fname, sizeof(fname)) != TRUE)
+               fname[0] = '\0';
+       bufp = eread("Insert file: ", fname, NFILEN,
+           EFNEW | EFCR | EFFILE | EFDEF);
+       if (bufp == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       adjf = adjustname(bufp, TRUE);
+       if (adjf == NULL)
+               return (FALSE);
+       return (insertfile(adjf, NULL, FALSE));
+}
+
+/*
+ * Select a file for editing.  Look around to see if you can find the file
+ * in another buffer; if you can find it, just switch to the buffer.  If
+ * you cannot find the file, create a new buffer, read in the text, and
+ * switch to the new buffer.
+ */
+/* ARGSUSED */
+int
+filevisit(int f, int n)
+{
+       struct buffer   *bp;
+       char     fname[NFILEN], *bufp, *adjf;
+       int      status;
+
+       if (getbufcwd(fname, sizeof(fname)) != TRUE)
+               fname[0] = '\0';
+       bufp = eread("Find file: ", fname, NFILEN,
+           EFNEW | EFCR | EFFILE | EFDEF);
+       if (bufp == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       adjf = adjustname(fname, TRUE);
+       if (adjf == NULL)
+               return (FALSE);
+       if ((bp = findbuffer(adjf)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       if (showbuffer(bp, curwp, WFFULL) != TRUE)
+               return (FALSE);
+       if (bp->b_fname[0] == '\0') {
+               if ((status = readin(adjf)) != TRUE)
+                       killbuffer(bp);
+               return (status);
+       }
+       return (TRUE);
+}
+
+/*
+ * Replace the current file with an alternate one. Semantics for finding
+ * the replacement file are the same as 'filevisit', except the current
+ * buffer is killed before the switch. If the kill fails, or is aborted,
+ * revert to the original file.
+ */
+/* ARGSUSED */
+int
+filevisitalt(int f, int n)
+{
+       struct buffer   *bp;
+       char     fname[NFILEN], *bufp, *adjf;
+       int      status;
+
+       if (getbufcwd(fname, sizeof(fname)) != TRUE)
+               fname[0] = '\0';
+       bufp = eread("Find alternate file: ", fname, NFILEN,
+           EFNEW | EFCR | EFFILE | EFDEF);
+       if (bufp == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+
+       status = killbuffer(curbp);
+       if (status == ABORT || status == FALSE)
+               return (ABORT);
+
+       adjf = adjustname(fname, TRUE);
+       if (adjf == NULL)
+               return (FALSE);
+       if ((bp = findbuffer(adjf)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       if (showbuffer(bp, curwp, WFFULL) != TRUE)
+               return (FALSE);
+       if (bp->b_fname[0] == '\0') {
+               if ((status = readin(adjf)) != TRUE)
+                       killbuffer(bp);
+               return (status);
+       }
+       return (TRUE);
+}
+
+int
+filevisitro(int f, int n)
+{
+       int error;
+
+       error = filevisit(f, n);
+       if (error != TRUE)
+               return (error);
+       curbp->b_flag |= BFREADONLY;
+       return (TRUE);
+}
+
+/*
+ * Pop to a file in the other window.  Same as the last function, but uses
+ * popbuf instead of showbuffer.
+ */
+/* ARGSUSED */
+int
+poptofile(int f, int n)
+{
+       struct buffer   *bp;
+       struct mgwin    *wp;
+       char     fname[NFILEN], *adjf, *bufp;
+       int      status;
+
+       if (getbufcwd(fname, sizeof(fname)) != TRUE)
+               fname[0] = '\0';
+       if ((bufp = eread("Find file in other window: ", fname, NFILEN,
+           EFNEW | EFCR | EFFILE | EFDEF)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       adjf = adjustname(fname, TRUE);
+       if (adjf == NULL)
+               return (FALSE);
+       if ((bp = findbuffer(adjf)) == NULL)
+               return (FALSE);
+       if (bp == curbp)
+               return (splitwind(f, n));
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       curwp = wp;
+       if (bp->b_fname[0] == '\0') {
+               if ((status = readin(adjf)) != TRUE)
+                       killbuffer(bp);
+               return (status);
+       }
+       return (TRUE);
+}
+
+/*
+ * Given a file name, either find the buffer it uses, or create a new
+ * empty buffer to put it in.
+ */
+struct buffer *
+findbuffer(char *fn)
+{
+       struct buffer   *bp;
+       char            bname[NBUFN], fname[NBUFN];
+
+       if (strlcpy(fname, fn, sizeof(fname)) >= sizeof(fname)) {
+               ewprintf("filename too long");
+               return (NULL);
+       }
+
+       for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
+               if (strcmp(bp->b_fname, fname) == 0)
+                       return (bp);
+       }
+       /* Not found. Create a new one, adjusting name first */
+       if (augbname(bname, fname, sizeof(bname)) == FALSE)
+               return (NULL);
+
+       bp = bfind(bname, TRUE);
+       return (bp);
+}
+
+/*
+ * Read the file "fname" into the current buffer.  Make all of the text
+ * in the buffer go away, after checking for unsaved changes.  This is
+ * called by the "read" command, the "visit" command, and the mainline
+ * (for "mg file").
+ */
+int
+readin(char *fname)
+{
+       struct mgwin    *wp;
+       int      status, i, ro = FALSE;
+       PF      *ael;
+
+       /* might be old */
+       if (bclear(curbp) != TRUE)
+               return (TRUE);
+       /* Clear readonly. May be set by autoexec path */
+       curbp->b_flag &= ~BFREADONLY;
+       if ((status = insertfile(fname, fname, TRUE)) != TRUE) {
+               ewprintf("File is not readable: %s", fname);
+               return (FALSE);
+       }
+
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+               if (wp->w_bufp == curbp) {
+                       wp->w_dotp = wp->w_linep = bfirstlp(curbp);
+                       wp->w_doto = 0;
+                       wp->w_markp = NULL;
+                       wp->w_marko = 0;
+               }
+       }
+
+       /*
+        * Call auto-executing function if we need to.
+        */
+       if ((ael = find_autoexec(fname)) != NULL) {
+               for (i = 0; ael[i] != NULL; i++)
+                       (*ael[i])(0, 1);
+               free(ael);
+       }
+
+       /* no change */
+       curbp->b_flag &= ~BFCHG;
+
+       /*
+        * We need to set the READONLY flag after we insert the file,
+        * unless the file is a directory.
+        */
+       if (access(fname, W_OK) && errno != ENOENT)
+               ro = TRUE;
+       if (fisdir(fname) == TRUE)
+               ro = TRUE;
+       if (ro == TRUE)
+               curbp->b_flag |= BFREADONLY;
+
+       if (startrow) {
+               gotoline(FFARG, startrow);
+               startrow = 0;
+       }
+
+       undo_add_modified();
+       return (status);
+}
+
+/*
+ * NB, getting file attributes is done here under control of a flag
+ * rather than in readin, which would be cleaner.  I was concerned
+ * that some operating system might require the file to be open
+ * in order to get the information.  Similarly for writing.
+ */
+
+/*
+ * Insert a file in the current buffer, after dot. If file is a directory,
+ * and 'replacebuf' is TRUE, invoke dired mode, else die with an error.
+ * If file is a regular file, set mark at the end of the text inserted;
+ * point at the beginning.  Return a standard status. Print a summary
+ * (lines read, error message) out as well. This routine also does the
+ * read end of backup processing.  The BFBAK flag, if set in a buffer,
+ * says that a backup should be taken.  It is set when a file is read in,
+ * but not on a new file. You don't need to make a backup copy of nothing.
+ */
+
+static char    *line = NULL;
+static int     linesize = 0;
+
+int
+insertfile(char *fname, char *newname, int replacebuf)
+{
+       struct buffer   *bp;
+       struct line     *lp1, *lp2;
+       struct line     *olp;                   /* line we started at */
+       struct mgwin    *wp;
+       int      nbytes, s, nline = 0, siz, x, x2;
+       int      opos;                  /* offset we started at */
+       int      oline;                 /* original line number */
+        FILE    *ffp;
+
+       if (replacebuf == TRUE)
+               x = undo_enable(FFRAND, 0);
+       else
+               x = undo_enabled();
+
+       lp1 = NULL;
+       if (line == NULL) {
+               line = malloc(NLINE);
+               if (line == NULL)
+                       panic("out of memory");
+               linesize = NLINE;
+       }
+
+       /* cheap */
+       bp = curbp;
+       if (newname != NULL) {
+               (void)strlcpy(bp->b_fname, newname, sizeof(bp->b_fname));
+               (void)xdirname(bp->b_cwd, newname, sizeof(bp->b_cwd));
+               (void)strlcat(bp->b_cwd, "/", sizeof(bp->b_cwd));
+       }
+
+       /* hard file open */
+       if ((s = ffropen(&ffp, fname, (replacebuf == TRUE) ? bp : NULL))
+           == FIOERR)
+               goto out;
+       if (s == FIOFNF) {
+               /* file not found */
+               if (newname != NULL)
+                       ewprintf("(New file)");
+               else
+                       ewprintf("(File not found)");
+               goto out;
+       } else if (s == FIODIR) {
+               /* file was a directory */
+               if (replacebuf == FALSE) {
+                       ewprintf("Cannot insert: file is a directory, %s",
+                           fname);
+                       goto cleanup;
+               }
+               killbuffer(bp);
+               bp = dired_(fname);
+               undo_enable(FFRAND, x);
+               if (bp == NULL)
+                       return (FALSE);
+               curbp = bp;
+               return (showbuffer(bp, curwp, WFFULL | WFMODE));
+       } else {
+               (void)xdirname(bp->b_cwd, fname, sizeof(bp->b_cwd));
+               (void)strlcat(bp->b_cwd, "/", sizeof(bp->b_cwd));
+       }
+       opos = curwp->w_doto;
+       oline = curwp->w_dotline;
+       /*
+        * Open a new line at dot and start inserting after it.
+        * We will delete this newline after insertion.
+        * Disable undo, as we create the undo record manually.
+        */
+       x2 = undo_enable(FFRAND, 0);
+       (void)lnewline();
+       olp = lback(curwp->w_dotp);
+       undo_enable(FFRAND, x2);
+
+       nline = 0;
+       siz = 0;
+       while ((s = ffgetline(ffp, line, linesize, &nbytes)) != FIOERR) {
+retry:
+               siz += nbytes + 1;
+               switch (s) {
+               case FIOSUC:
+                       /* FALLTHRU */
+               case FIOEOF:
+                       ++nline;
+                       if ((lp1 = lalloc(nbytes)) == NULL) {
+                               /* keep message on the display */
+                               s = FIOERR;
+                               undo_add_insert(olp, opos,
+                                   siz - nbytes - 1 - 1);
+                               goto endoffile;
+                       }
+                       bcopy(line, &ltext(lp1)[0], nbytes);
+                       lp2 = lback(curwp->w_dotp);
+                       lp2->l_fp = lp1;
+                       lp1->l_fp = curwp->w_dotp;
+                       lp1->l_bp = lp2;
+                       curwp->w_dotp->l_bp = lp1;
+                       if (s == FIOEOF) {
+                               undo_add_insert(olp, opos, siz - 1);
+                               goto endoffile;
+                       }
+                       break;
+               case FIOLONG: {
+                               /* a line too long to fit in our buffer */
+                               char    *cp;
+                               int     newsize;
+
+                               newsize = linesize * 2;
+                               if (newsize < 0 ||
+                                   (cp = malloc(newsize)) == NULL) {
+                                       ewprintf("Could not allocate %d bytes",
+                                           newsize);
+                                               s = FIOERR;
+                                               goto endoffile;
+                               }
+                               bcopy(line, cp, linesize);
+                               free(line);
+                               line = cp;
+                               s = ffgetline(ffp, line + linesize, linesize,
+                                   &nbytes);
+                               nbytes += linesize;
+                               linesize = newsize;
+                               if (s == FIOERR)
+                                       goto endoffile;
+                               goto retry;
+                       }
+               default:
+                       ewprintf("Unknown code %d reading file", s);
+                       s = FIOERR;
+                       break;
+               }
+       }
+endoffile:
+       /* ignore errors */
+       (void)ffclose(ffp, NULL);
+       /* don't zap an error */
+       if (s == FIOEOF) {
+               if (nline == 1)
+                       ewprintf("(Read 1 line)");
+               else
+                       ewprintf("(Read %d lines)", nline);
+       }
+       /* set mark at the end of the text */
+       curwp->w_dotp = curwp->w_markp = lback(curwp->w_dotp);
+       curwp->w_marko = llength(curwp->w_markp);
+       curwp->w_markline = oline + nline + 1;
+       /*
+        * if we are at the end of the file, ldelnewline is a no-op,
+        * but we still need to decrement the line and markline counts
+        * as we've accounted for this fencepost in our arithmetic
+        */
+       if (lforw(curwp->w_dotp) == curwp->w_bufp->b_headp) {
+               curwp->w_bufp->b_lines--;
+               curwp->w_markline--;
+       } else
+               (void)ldelnewline();
+       curwp->w_dotp = olp;
+       curwp->w_doto = opos;
+       curwp->w_dotline = oline;
+       if (olp == curbp->b_headp)
+               curwp->w_dotp = lforw(olp);
+       if (newname != NULL)
+               bp->b_flag |= BFCHG | BFBAK;    /* Need a backup.        */
+       else
+               bp->b_flag |= BFCHG;
+       /*
+        * If the insert was at the end of buffer, set lp1 to the end of
+        * buffer line, and lp2 to the beginning of the newly inserted text.
+        * (Otherwise lp2 is set to NULL.)  This is used below to set
+        * pointers in other windows correctly if they are also at the end of
+        * buffer.
+        */
+       lp1 = bp->b_headp;
+       if (curwp->w_markp == lp1) {
+               lp2 = curwp->w_dotp;
+       } else {
+               /* delete extraneous newline */
+               (void)ldelnewline();
+out:           lp2 = NULL;
+       }
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+               if (wp->w_bufp == curbp) {
+                       wp->w_rflag |= WFMODE | WFEDIT;
+                       if (wp != curwp && lp2 != NULL) {
+                               if (wp->w_dotp == lp1)
+                                       wp->w_dotp = lp2;
+                               if (wp->w_markp == lp1)
+                                       wp->w_markp = lp2;
+                               if (wp->w_linep == lp1)
+                                       wp->w_linep = lp2;
+                       }
+               }
+       }
+       bp->b_lines += nline;
+cleanup:
+       undo_enable(FFRAND, x);
+
+       /* return FALSE if error */
+       return (s != FIOERR);
+}
+
+/*
+ * Ask for a file name and write the contents of the current buffer to that
+ * file.  Update the remembered file name and clear the buffer changed flag.
+ * This handling of file names is different from the earlier versions and
+ * is more compatible with Gosling EMACS than with ITS EMACS.
+ */
+/* ARGSUSED */
+int
+filewrite(int f, int n)
+{
+       struct stat     statbuf;
+       int      s;
+       char     fname[NFILEN], bn[NBUFN], tmp[NFILEN + 25];
+       char    *adjfname, *bufp;
+        FILE    *ffp;
+
+       if (getbufcwd(fname, sizeof(fname)) != TRUE)
+               fname[0] = '\0';
+       if ((bufp = eread("Write file: ", fname, NFILEN,
+           EFDEF | EFNEW | EFCR | EFFILE)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+
+       adjfname = adjustname(fname, TRUE);
+       if (adjfname == NULL)
+               return (FALSE);
+
+        /* Check if file exists; write checks done later */
+        if (stat(adjfname, &statbuf) == 0) {
+               snprintf(tmp, sizeof(tmp), "File `%s' exists; overwrite",
+                   adjfname);
+               if ((s = eyorn(tmp)) != TRUE)
+                        return (s);
+        }
+
+       /* old attributes are no longer current */
+       bzero(&curbp->b_fi, sizeof(curbp->b_fi));
+       if ((s = writeout(&ffp, curbp, adjfname)) == TRUE) {
+               (void)strlcpy(curbp->b_fname, adjfname, sizeof(curbp->b_fname));
+               if (getbufcwd(curbp->b_cwd, sizeof(curbp->b_cwd)) != TRUE)
+                       (void)strlcpy(curbp->b_cwd, "/", sizeof(curbp->b_cwd));
+               if (augbname(bn, curbp->b_fname, sizeof(bn))
+                   == FALSE)
+                       return (FALSE);
+               free(curbp->b_bname);
+               if ((curbp->b_bname = strdup(bn)) == NULL)
+                       return (FALSE);
+               (void)fupdstat(curbp);
+               curbp->b_flag &= ~(BFBAK | BFCHG);
+               upmodes(curbp);
+       }
+       return (s);
+}
+
+/*
+ * Save the contents of the current buffer back into its associated file.
+ */
+#ifndef        MAKEBACKUP
+#define        MAKEBACKUP TRUE
+#endif /* !MAKEBACKUP */
+static int     makebackup = MAKEBACKUP;
+
+/* ARGSUSED */
+int
+filesave(int f, int n)
+{
+       if (curbp->b_fname[0] == '\0')
+               return (filewrite(f, n));
+       else
+               return (buffsave(curbp));
+}
+
+/*
+ * Save the contents of the buffer argument into its associated file.  Do
+ * nothing if there have been no changes (is this a bug, or a feature?).
+ * Error if there is no remembered file name. If this is the first write
+ * since the read or visit, then a backup copy of the file is made.
+ * Allow user to select whether or not to make backup files by looking at
+ * the value of makebackup.
+ */
+int
+buffsave(struct buffer *bp)
+{
+       int      s;
+        FILE    *ffp;
+
+       /* return, no changes */
+       if ((bp->b_flag & BFCHG) == 0) {
+               ewprintf("(No changes need to be saved)");
+               return (TRUE);
+       }
+
+       /* must have a name */
+       if (bp->b_fname[0] == '\0') {
+               ewprintf("No file name");
+               return (FALSE);
+       }
+
+       /* Ensure file has not been modified elsewhere */
+       /* We don't use the ignore flag here */
+       if (fchecktime(bp) != TRUE) {
+               if ((s = eyesno("File has changed on disk since last save. "
+                   "Save anyway")) != TRUE)
+                       return (s);
+       }
+       
+       if (makebackup && (bp->b_flag & BFBAK)) {
+               s = fbackupfile(bp->b_fname);
+               /* hard error */
+               if (s == ABORT)
+                       return (FALSE);
+               /* softer error */
+               if (s == FALSE &&
+                   (s = eyesno("Backup error, save anyway")) != TRUE)
+                       return (s);
+       }
+       if ((s = writeout(&ffp, bp, bp->b_fname)) == TRUE) {
+               (void)fupdstat(bp);
+               bp->b_flag &= ~(BFCHG | BFBAK);
+               upmodes(bp);
+       }
+       return (s);
+}
+
+/*
+ * Since we don't have variables (we probably should) this is a command
+ * processor for changing the value of the make backup flag.  If no argument
+ * is given, sets makebackup to true, so backups are made.  If an argument is
+ * given, no backup files are made when saving a new version of a file.
+ */
+/* ARGSUSED */
+int
+makebkfile(int f, int n)
+{
+       if (f & FFARG)
+               makebackup = n > 0;
+       else
+               makebackup = !makebackup;
+       ewprintf("Backup files %sabled", makebackup ? "en" : "dis");
+       return (TRUE);
+}
+
+/*
+ * NB: bp is passed to both ffwopen and ffclose because some
+ * attribute information may need to be updated at open time
+ * and others after the close.  This is OS-dependent.  Note
+ * that the ff routines are assumed to be able to tell whether
+ * the attribute information has been set up in this buffer
+ * or not.
+ */
+
+/*
+ * This function performs the details of file writing; writing the file
+ * in buffer bp to file fn. Uses the file management routines in the
+ * "fileio.c" package. Most of the grief is checking of some sort.
+ * You may want to call fupdstat() after using this function.
+ */
+int
+writeout(FILE ** ffp, struct buffer *bp, char *fn)
+{
+       int      s;
+
+       /* open writes message */
+       if ((s = ffwopen(ffp, fn, bp)) != FIOSUC)
+               return (FALSE);
+       s = ffputbuf(*ffp, bp);
+       if (s == FIOSUC) {
+               /* no write error */
+               s = ffclose(*ffp, bp);
+               if (s == FIOSUC)
+                       ewprintf("Wrote %s", fn);
+       } else {
+               /* print a message indicating write error */
+               (void)ffclose(*ffp, bp);
+               ewprintf("Unable to write %s", fn);
+       }
+       return (s == FIOSUC);
+}
+
+/*
+ * Tag all windows for bp (all windows if bp == NULL) as needing their
+ * mode line updated.
+ */
+void
+upmodes(struct buffer *bp)
+{
+       struct mgwin    *wp;
+
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp)
+               if (bp == NULL || curwp->w_bufp == bp)
+                       wp->w_rflag |= WFMODE;
+}
+
+/*
+ * dirname using strlcpy semantic.
+ * Like dirname() except an empty string is returned in
+ * place of "/". This means we can always add a trailing
+ * slash and be correct.
+ * Address portability issues by copying argument
+ * before using. Some implementations modify the input string.
+ */
+size_t
+xdirname(char *dp, const char *path, size_t dplen)
+{
+       char ts[NFILEN];
+       size_t len;
+
+       (void)strlcpy(ts, path, NFILEN);
+       len = strlcpy(dp, dirname(ts), dplen);
+       if (dplen > 0 && dp[0] == '/' && dp[1] == '\0') {
+               dp[0] = '\0';
+               len = 0;
+       }
+       return (len);
+}
+
+/*
+ * basename using strlcpy/strlcat semantic.
+ * Address portability issue by copying argument
+ * before using: some implementations modify the input string.
+ */
+size_t
+xbasename(char *bp, const char *path, size_t bplen)
+{
+       char ts[NFILEN];
+
+       (void)strlcpy(ts, path, NFILEN);
+       return (strlcpy(bp, basename(ts), bplen));
+}
diff --git a/mg/fileio.c b/mg/fileio.c
new file mode 100644 (file)
index 0000000..0b63be8
--- /dev/null
@@ -0,0 +1,754 @@
+/*     $OpenBSD: fileio.c,v 1.94 2012/07/10 06:28:12 lum Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ *     POSIX fileio.c
+ */
+#include "def.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <limits.h>
+#include <dirent.h>
+#include <pwd.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "kbd.h"
+#include "pathnames.h"
+
+static char *bkuplocation(const char *);
+static int   bkupleavetmp(const char *);
+char       *expandtilde(const char *);
+
+static char *bkupdir;
+static int   leavetmp = 0;     /* 1 = leave any '~' files in tmp dir */
+
+/*
+ * Open a file for reading.
+ */
+int
+ffropen(FILE ** ffp, const char *fn, struct buffer *bp)
+{
+       if ((*ffp = fopen(fn, "r")) == NULL) {
+               if (errno == ENOENT)
+                       return (FIOFNF);
+               return (FIOERR);
+       }
+
+       /* If 'fn' is a directory open it with dired. */
+       if (fisdir(fn) == TRUE)
+               return (FIODIR);
+
+       ffstat(*ffp, bp);
+
+       return (FIOSUC);
+}
+
+/*
+ * Update stat/dirty info
+ */
+void
+ffstat(FILE *ffp, struct buffer *bp)
+{
+       struct stat     sb;
+
+       if (bp && fstat(fileno(ffp), &sb) == 0) {
+               /* set highorder bit to make sure this isn't all zero */
+               bp->b_fi.fi_mode = sb.st_mode | 0x8000;
+               bp->b_fi.fi_uid = sb.st_uid;
+               bp->b_fi.fi_gid = sb.st_gid;
+               bp->b_fi.fi_mtime = sb.st_mtimespec;
+               /* Clear the ignore flag */
+               bp->b_flag &= ~(BFIGNDIRTY | BFDIRTY);
+       }
+}
+
+/*
+ * Update the status/dirty info. If there is an error,
+ * there's not a lot we can do.
+ */
+int
+fupdstat(struct buffer *bp)
+{
+       FILE *ffp;
+
+       if ((ffp = fopen(bp->b_fname, "r")) == NULL) {
+               if (errno == ENOENT)
+                       return (FIOFNF);
+               return (FIOERR);
+       }
+       ffstat(ffp, bp);
+       (void)ffclose(ffp, bp);
+       return (FIOSUC);
+}
+
+/*
+ * Open a file for writing.
+ */
+int
+ffwopen(FILE ** ffp, const char *fn, struct buffer *bp)
+{
+       int     fd;
+       mode_t  fmode = DEFFILEMODE;
+
+       if (bp && bp->b_fi.fi_mode)
+               fmode = bp->b_fi.fi_mode & 07777;
+
+       fd = open(fn, O_RDWR | O_CREAT | O_TRUNC, fmode);
+       if (fd == -1) {
+               ffp = NULL;
+               ewprintf("Cannot open file for writing : %s", strerror(errno));
+               return (FIOERR);
+       }
+
+       if ((*ffp = fdopen(fd, "w")) == NULL) {
+               ewprintf("Cannot open file for writing : %s", strerror(errno));
+               close(fd);
+               return (FIOERR);
+       }
+
+       /*
+        * If we have file information, use it.  We don't bother to check for
+        * errors, because there's no a lot we can do about it.  Certainly
+        * trying to change ownership will fail if we aren't root.  That's
+        * probably OK.  If we don't have info, no need to get it, since any
+        * future writes will do the same thing.
+        */
+       if (bp && bp->b_fi.fi_mode) {
+               fchmod(fd, bp->b_fi.fi_mode & 07777);
+               fchown(fd, bp->b_fi.fi_uid, bp->b_fi.fi_gid);
+       }
+       return (FIOSUC);
+}
+
+/*
+ * Close a file.
+ */
+/* ARGSUSED */
+int
+ffclose(FILE *ffp, struct buffer *bp)
+{
+       if (fclose(ffp) == 0)
+               return (FIOSUC);
+       return (FIOERR);
+}
+
+/*
+ * Write a buffer to the already opened file. bp points to the
+ * buffer. Return the status.
+ */
+int
+ffputbuf(FILE *ffp, struct buffer *bp)
+{
+       struct line   *lp, *lpend;
+
+       lpend = bp->b_headp;
+       for (lp = lforw(lpend); lp != lpend; lp = lforw(lp)) {
+               if (fwrite(ltext(lp), 1, llength(lp), ffp) != llength(lp)) {
+                       ewprintf("Write I/O error");
+                       return (FIOERR);
+               }
+               if (lforw(lp) != lpend)         /* no implied \n on last line */
+                       putc('\n', ffp);
+       }
+       /*
+        * XXX should be variable controlled (once we have variables)
+        */
+       if (llength(lback(lpend)) != 0) {
+               if (eyorn("No newline at end of file, add one") == TRUE) {
+                       lnewline_at(lback(lpend), llength(lback(lpend)));
+                       putc('\n', ffp);
+               }
+       }
+       return (FIOSUC);
+}
+
+/*
+ * Read a line from a file, and store the bytes
+ * in the supplied buffer. Stop on end of file or end of
+ * line.  When FIOEOF is returned, there is a valid line
+ * of data without the normally implied \n.
+ * If the line length exceeds nbuf, FIOLONG is returned.
+ */
+int
+ffgetline(FILE *ffp, char *buf, int nbuf, int *nbytes)
+{
+       int     c, i;
+
+       i = 0;
+       while ((c = getc(ffp)) != EOF && c != '\n') {
+               buf[i++] = c;
+               if (i >= nbuf)
+                       return (FIOLONG);
+       }
+       if (c == EOF && ferror(ffp) != FALSE) {
+               ewprintf("File read error");
+               return (FIOERR);
+       }
+       *nbytes = i;
+       return (c == EOF ? FIOEOF : FIOSUC);
+}
+
+/*
+ * Make a backup copy of "fname".  On Unix the backup has the same
+ * name as the original file, with a "~" on the end; this seems to
+ * be newest of the new-speak. The error handling is all in "file.c".
+ * We do a copy instead of a rename since otherwise another process
+ * with an open fd will get the backup, not the new file.  This is
+ * a problem when using mg with things like crontab and vipw.
+ */
+int
+fbackupfile(const char *fn)
+{
+       struct stat      sb;
+       int              from, to, serrno;
+       ssize_t          nread;
+       char             buf[BUFSIZ];
+       char            *nname, *tname, *bkpth;
+
+       if (stat(fn, &sb) == -1) {
+               ewprintf("Can't stat %s : %s", fn, strerror(errno));
+               return (FALSE);
+       }
+
+       if ((bkpth = bkuplocation(fn)) == NULL)
+               return (FALSE);
+
+       if (asprintf(&nname, "%s~", bkpth) == -1) {
+               ewprintf("Can't allocate backup file name : %s", strerror(errno));
+               free(bkpth);
+               return (ABORT);
+       }
+       if (asprintf(&tname, "%s.XXXXXXXXXX", bkpth) == -1) {
+               ewprintf("Can't allocate temp file name : %s", strerror(errno));
+               free(bkpth);
+               free(nname);
+               return (ABORT);
+       }
+       free(bkpth);
+
+       if ((from = open(fn, O_RDONLY)) == -1) {
+               free(nname);
+               free(tname);
+               return (FALSE);
+       }
+       to = mkstemp(tname);
+       if (to == -1) {
+               serrno = errno;
+               close(from);
+               free(nname);
+               free(tname);
+               errno = serrno;
+               return (FALSE);
+       }
+       while ((nread = read(from, buf, sizeof(buf))) > 0) {
+               if (write(to, buf, (size_t)nread) != nread) {
+                       nread = -1;
+                       break;
+               }
+       }
+       serrno = errno;
+       (void) fchmod(to, (sb.st_mode & 0777));
+       close(from);
+       close(to);
+       if (nread == -1) {
+               if (unlink(tname) == -1)
+                       ewprintf("Can't unlink temp : %s", strerror(errno));
+       } else {
+               if (rename(tname, nname) == -1) {
+                       ewprintf("Can't rename temp : %s", strerror(errno));
+                       (void) unlink(tname);
+                       nread = -1;
+               }
+       }
+       free(nname);
+       free(tname);
+       errno = serrno;
+
+       return (nread == -1 ? FALSE : TRUE);
+}
+
+/*
+ * Convert "fn" to a canonicalized absolute filename, replacing
+ * a leading ~/ with the user's home dir, following symlinks, and
+ * and remove all occurrences of /./ and /../
+ */
+char *
+adjustname(const char *fn, int slashslash)
+{
+       static char      fnb[MAXPATHLEN];
+       const char      *cp, *ep = NULL;
+       char            *path;
+
+       if (slashslash == TRUE) {
+               cp = fn + strlen(fn) - 1;
+               for (; cp >= fn; cp--) {
+                       if (ep && (*cp == '/')) {
+                               fn = ep;
+                               break;
+                       }
+                       if (*cp == '/' || *cp == '~')
+                               ep = cp;
+                       else
+                               ep = NULL;
+               }
+       }
+       if ((path = expandtilde(fn)) == NULL)
+               return (NULL);
+
+       if (realpath(path, fnb) == NULL)
+               (void)strlcpy(fnb, path, sizeof(fnb));
+
+       free(path);
+       return (fnb);
+}
+
+/*
+ * Find a startup file for the user and return its name. As a service
+ * to other pieces of code that may want to find a startup file (like
+ * the terminal driver in particular), accepts a suffix to be appended
+ * to the startup file name.
+ */
+char *
+startupfile(char *suffix)
+{
+       static char      file[NFILEN];
+       char            *home;
+       int              ret;
+
+       if ((home = getenv("HOME")) == NULL || *home == '\0')
+               goto nohome;
+
+       if (suffix == NULL) {
+               ret = snprintf(file, sizeof(file), _PATH_MG_STARTUP, home);
+               if (ret < 0 || ret >= sizeof(file))
+                       return (NULL);
+       } else {
+               ret = snprintf(file, sizeof(file), _PATH_MG_TERM, home, suffix);
+               if (ret < 0 || ret >= sizeof(file))
+                       return (NULL);
+       }
+
+       if (access(file, R_OK) == 0)
+               return (file);
+nohome:
+#ifdef STARTUPFILE
+       if (suffix == NULL) {
+               ret = snprintf(file, sizeof(file), "%s", STARTUPFILE);
+               if (ret < 0 || ret >= sizeof(file))
+                       return (NULL);
+       } else {
+               ret = snprintf(file, sizeof(file), "%s%s", STARTUPFILE,
+                   suffix);
+               if (ret < 0 || ret >= sizeof(file))
+                       return (NULL);
+       }
+
+       if (access(file, R_OK) == 0)
+               return (file);
+#endif /* STARTUPFILE */
+       return (NULL);
+}
+
+int
+copy(char *frname, char *toname)
+{
+       int     ifd, ofd;
+       char    buf[BUFSIZ];
+       mode_t  fmode = DEFFILEMODE;    /* XXX?? */
+       struct  stat orig;
+       ssize_t sr;
+
+       if ((ifd = open(frname, O_RDONLY)) == -1)
+               return (FALSE);
+       if (fstat(ifd, &orig) == -1) {
+               ewprintf("fstat: %s", strerror(errno));
+               close(ifd);
+               return (FALSE);
+       }
+
+       if ((ofd = open(toname, O_WRONLY|O_CREAT|O_TRUNC, fmode)) == -1) {
+               close(ifd);
+               return (FALSE);
+       }
+       while ((sr = read(ifd, buf, sizeof(buf))) > 0) {
+               if (write(ofd, buf, (size_t)sr) != sr) {
+                       ewprintf("write error : %s", strerror(errno));
+                       break;
+               }
+       }
+       if (fchmod(ofd, orig.st_mode) == -1)
+               ewprintf("Cannot set original mode : %s", strerror(errno));
+
+       if (sr == -1) {
+               ewprintf("Read error : %s", strerror(errno));
+               close(ifd);
+               close(ofd);
+               return (FALSE);
+       }
+       /*
+        * It is "normal" for this to fail since we can't guarantee that
+        * we will be running as root.
+        */
+       if (fchown(ofd, orig.st_uid, orig.st_gid) && errno != EPERM)
+               ewprintf("Cannot set owner : %s", strerror(errno));
+
+       (void) close(ifd);
+       (void) close(ofd);
+
+       return (TRUE);
+}
+
+/*
+ * return list of file names that match the name in buf.
+ */
+struct list *
+make_file_list(char *buf)
+{
+       char            *dir, *file, *cp;
+       size_t           len, preflen;
+       int              ret;
+       DIR             *dirp;
+       struct dirent   *dent;
+       struct list     *last, *current;
+       char             fl_name[NFILEN + 2];
+       char             prefixx[NFILEN + 1];
+
+       /*
+        * We need three different strings:
+
+        * dir - the name of the directory containing what the user typed.
+        *  Must be a real unix file name, e.g. no ~user, etc..
+        *  Must not end in /.
+        * prefix - the portion of what the user typed that is before the
+        *  names we are going to find in the directory.  Must have a
+        * trailing / if the user typed it.
+        * names from the directory - We open dir, and return prefix
+        * concatenated with names.
+        */
+
+       /* first we get a directory name we can look up */
+       /*
+        * Names ending in . are potentially odd, because adjustname will
+        * treat foo/bar/.. as a foo/, whereas we are
+        * interested in names starting with ..
+        */
+       len = strlen(buf);
+       if (len && buf[len - 1] == '.') {
+               buf[len - 1] = 'x';
+               dir = adjustname(buf, TRUE);
+               buf[len - 1] = '.';
+       } else
+               dir = adjustname(buf, TRUE);
+       if (dir == NULL)
+               return (NULL);
+       /*
+        * If the user typed a trailing / or the empty string
+        * he wants us to use his file spec as a directory name.
+        */
+       if (len && buf[len - 1] != '/') {
+               file = strrchr(dir, '/');
+               if (file) {
+                       *file = '\0';
+                       if (*dir == '\0')
+                               dir = "/";
+               } else
+                       return (NULL);
+       }
+       /* Now we get the prefix of the name the user typed. */
+       if (strlcpy(prefixx, buf, sizeof(prefixx)) >= sizeof(prefixx))
+               return (NULL);
+       cp = strrchr(prefixx, '/');
+       if (cp == NULL)
+               prefixx[0] = '\0';
+       else
+               cp[1] = '\0';
+
+       preflen = strlen(prefixx);
+       /* cp is the tail of buf that really needs to be compared. */
+       cp = buf + preflen;
+       len = strlen(cp);
+
+       /*
+        * Now make sure that file names will fit in the buffers allocated.
+        * SV files are fairly short.  For BSD, something more general would
+        * be required.
+        */
+       if (preflen > NFILEN - MAXNAMLEN)
+               return (NULL);
+
+       /* loop over the specified directory, making up the list of files */
+
+       /*
+        * Note that it is worth our time to filter out names that don't
+        * match, even though our caller is going to do so again, and to
+        * avoid doing the stat if completion is being done, because stat'ing
+        * every file in the directory is relatively expensive.
+        */
+
+       dirp = opendir(dir);
+       if (dirp == NULL)
+               return (NULL);
+       last = NULL;
+
+       while ((dent = readdir(dirp)) != NULL) {
+               int isdir;
+               if (strncmp(cp, dent->d_name, len) != 0)
+                       continue;
+               isdir = 0;
+               if (dent->d_type == DT_DIR) {
+                       isdir = 1;
+               } else if (dent->d_type == DT_LNK ||
+                           dent->d_type == DT_UNKNOWN) {
+                       struct stat     statbuf;
+                       char            statname[NFILEN + 2];
+
+                       statbuf.st_mode = 0;
+                       ret = snprintf(statname, sizeof(statname), "%s/%s",
+                           dir, dent->d_name);
+                       if (ret < 0 || ret > sizeof(statname) - 1)
+                               continue;
+                       if (stat(statname, &statbuf) < 0)
+                               continue;
+                       if (S_ISDIR(statbuf.st_mode))
+                               isdir = 1;
+               }
+
+               if ((current = malloc(sizeof(struct list))) == NULL) {
+                       free_file_list(last);
+                       closedir(dirp);
+                       return (NULL);
+               }
+               ret = snprintf(fl_name, sizeof(fl_name),
+                   "%s%s%s", prefixx, dent->d_name, isdir ? "/" : "");
+               if (ret < 0 || ret >= sizeof(fl_name)) {
+                       free(current);
+                       continue;
+               }
+               current->l_next = last;
+               current->l_name = strdup(fl_name);
+               last = current;
+       }
+       closedir(dirp);
+
+       return (last);
+}
+
+/*
+ * Test if a supplied filename refers to a directory
+ * Returns ABORT on error, TRUE if directory. FALSE otherwise
+ */
+int
+fisdir(const char *fname)
+{
+       struct stat     statbuf;
+
+       if (stat(fname, &statbuf) != 0)
+               return (ABORT);
+
+       if (S_ISDIR(statbuf.st_mode))
+               return (TRUE);
+
+       return (FALSE);
+}
+
+/*
+ * Check the mtime of the supplied filename.
+ * Return TRUE if last mtime matches, FALSE if not,
+ * If the stat fails, return TRUE and try the save anyway
+ */
+int
+fchecktime(struct buffer *bp)
+{
+       struct stat sb;
+
+       if (stat(bp->b_fname, &sb) == -1)
+               return (TRUE);
+
+       if (bp->b_fi.fi_mtime.tv_sec != sb.st_mtimespec.tv_sec ||
+           bp->b_fi.fi_mtime.tv_nsec != sb.st_mtimespec.tv_nsec)
+               return (FALSE);
+
+       return (TRUE);
+
+}
+
+/*
+ * Location of backup file. This function creates the correct path.
+ */
+static char *
+bkuplocation(const char *fn)
+{
+       struct stat sb;
+       char *ret;
+
+       if (bkupdir != NULL && (stat(bkupdir, &sb) == 0) &&
+           S_ISDIR(sb.st_mode) && !bkupleavetmp(fn)) {
+               char fname[NFILEN];
+               const char *c;
+               int i = 0, len;
+
+               c = fn;
+               len = strlen(bkupdir);
+
+               while (*c != '\0') {
+                       /* Make sure we don't go over combined:
+                       * strlen(bkupdir + '/' + fname + '\0')
+                       */
+                       if (i >= NFILEN - len - 1)
+                               return (NULL);
+                       if (*c == '/') {
+                               fname[i] = '!';
+                       } else if (*c == '!') {
+                               if (i >= NFILEN - len - 2)
+                                       return (NULL);
+                               fname[i++] = '!';
+                               fname[i] = '!';
+                       } else
+                               fname[i] = *c;
+                       i++;
+                       c++;
+               }
+               fname[i] = '\0';
+               if (asprintf(&ret, "%s/%s", bkupdir, fname) == -1)
+                       return (NULL);
+
+       } else if ((ret = strndup(fn, NFILEN)) == NULL)
+               return (NULL);
+
+       return (ret);
+}
+
+int
+backuptohomedir(int f, int n)
+{
+       const char      *c = _PATH_MG_DIR;
+       char            *p;
+
+       if (bkupdir == NULL) {
+               p = adjustname(c, TRUE);
+               bkupdir = strndup(p, NFILEN);
+               if (bkupdir == NULL)
+                       return(FALSE);
+
+               if (mkdir(bkupdir, 0700) == -1 && errno != EEXIST) {
+                       free(bkupdir);
+                       bkupdir = NULL;
+               }
+       } else {
+               free(bkupdir);
+               bkupdir = NULL;
+       }
+
+       return (TRUE);
+}
+
+/*
+ * For applications that use mg as the editor and have a desire to keep
+ * '~' files in the TMPDIR, toggle the location: /tmp | ~/.mg.d
+ */
+int
+toggleleavetmp(int f, int n)
+{
+       leavetmp = !leavetmp;
+
+       return (TRUE);
+}
+
+/*
+ * Returns TRUE if fn is located in the temp directory and we want to save
+ * those backups there.
+ */
+int
+bkupleavetmp(const char *fn)
+{
+       char    *tmpdir, *tmp = NULL;
+
+       if (!leavetmp)
+               return(FALSE);
+
+       if((tmpdir = getenv("TMPDIR")) != NULL && *tmpdir != '\0') {
+               tmp = strstr(fn, tmpdir);
+               if (tmp == fn)
+                       return (TRUE);
+
+               return (FALSE);
+       }
+
+       tmp = strstr(fn, "/tmp");
+       if (tmp == fn)
+               return (TRUE);
+
+       return (FALSE);
+}
+
+/*
+ * Expand file names beginning with '~' if appropriate:
+ *   1, if ./~fn exists, continue without expanding tilde.
+ *   2, else, if username 'fn' exists, expand tilde with home directory path.
+ *   3, otherwise, continue and create new buffer called ~fn.
+ */
+char *
+expandtilde(const char *fn)
+{
+       struct passwd   *pw;
+       struct stat      statbuf;
+       const char      *cp;
+       char             user[LOGIN_NAME_MAX], path[NFILEN];
+       char            *un, *ret;
+       size_t           ulen, plen;
+
+       path[0] = '\0';
+
+       if (fn[0] != '~' || stat(fn, &statbuf) == 0) {
+               if ((ret = strndup(fn, NFILEN)) == NULL)
+                       return (NULL);
+               return(ret);
+       }
+       cp = strchr(fn, '/');
+       if (cp == NULL)
+               cp = fn + strlen(fn); /* point to the NUL byte */
+       ulen = cp - &fn[1];
+       if (ulen >= sizeof(user)) {
+               if ((ret = strndup(fn, NFILEN)) == NULL)
+                       return (NULL);
+               return(ret);
+       }
+       if (ulen == 0) { /* ~/ or ~ */
+               if ((un = getlogin()) != NULL)
+                       (void)strlcpy(user, un, sizeof(user));
+               else
+                       user[0] = '\0';
+       } else { /* ~user/ or ~user */
+               memcpy(user, &fn[1], ulen);
+               user[ulen] = '\0';
+       }
+       pw = getpwnam(user);
+       if (pw != NULL) {
+               plen = strlcpy(path, pw->pw_dir, sizeof(path));
+               if (plen == 0 || path[plen - 1] != '/') {
+                       if (strlcat(path, "/", sizeof(path)) >= sizeof(path)) {
+                               ewprintf("Path too long");
+                               return (NULL);
+                       }
+               }
+               fn = cp;
+               if (*fn == '/')
+                       fn++;
+       }
+       if (strlcat(path, fn, sizeof(path)) >= sizeof(path)) {
+               ewprintf("Path too long");
+               return (NULL);
+       }
+       if ((ret = strndup(path, NFILEN)) == NULL)
+               return (NULL);
+
+       return (ret);
+}
diff --git a/mg/funmap.c b/mg/funmap.c
new file mode 100644 (file)
index 0000000..aac58ce
--- /dev/null
@@ -0,0 +1,281 @@
+/*     $OpenBSD: funmap.c,v 1.40 2012/06/14 17:21:22 lum Exp $ */
+
+/* This file is in the public domain */
+
+#include "def.h"
+#include "kbd.h"
+#include "funmap.h"
+
+/*
+ * If the function is NULL, it must be listed with the
+ * same name in the map_table.
+ */
+
+struct funmap {
+       PF               fn_funct;
+       const            char *fn_name;
+       struct funmap   *fn_next;
+};
+
+static struct funmap *funs;
+
+static struct funmap functnames[] = {
+       {apropos_command, "apropos",},
+       {auto_execute, "auto-execute", },
+       {fillmode, "auto-fill-mode",},
+       {indentmode, "auto-indent-mode",},
+       {backtoindent, "back-to-indentation",},
+       {backuptohomedir, "backup-to-home-directory",},
+       {backchar, "backward-char",},
+       {delbword, "backward-kill-word",},
+       {gotobop, "backward-paragraph",},
+       {backword, "backward-word",},
+       {gotobob, "beginning-of-buffer",},
+       {gotobol, "beginning-of-line",},
+       {showmatch, "blink-and-insert",},
+       {bsmap, "bsmap-mode",},
+       {NULL, "c-x 4 prefix",},
+       {NULL, "c-x prefix",},
+       {executemacro, "call-last-kbd-macro",},
+       {capword, "capitalize-word",},
+       {changedir, "cd",},
+       {clearmark, "clear-mark",},
+       {copyregion, "copy-region-as-kill",},
+#ifdef REGEX
+       {cntmatchlines, "count-matches",},
+       {cntnonmatchlines, "count-non-matches",},
+#endif /* REGEX */
+       {redefine_key, "define-key",},
+       {backdel, "delete-backward-char",},
+       {deblank, "delete-blank-lines",},
+       {forwdel, "delete-char",},
+       {delwhite, "delete-horizontal-space",},
+       {delleadwhite, "delete-leading-space",},
+       {deltrailwhite, "delete-trailing-space",},
+#ifdef REGEX
+       {delmatchlines, "delete-matching-lines",},
+       {delnonmatchlines, "delete-non-matching-lines",},
+#endif /* REGEX */
+       {onlywind, "delete-other-windows",},
+       {delwind, "delete-window",},
+       {wallchart, "describe-bindings",},
+       {desckey, "describe-key-briefly",},
+       {digit_argument, "digit-argument",},
+       {lowerregion, "downcase-region",},
+       {lowerword, "downcase-word",},
+       {showversion, "emacs-version",},
+       {finishmacro, "end-kbd-macro",},
+       {globalwdtoggle, "global-wd-mode",},
+       {gotoeob, "end-of-buffer",},
+       {gotoeol, "end-of-line",},
+       {enlargewind, "enlarge-window",},
+       {NULL, "esc prefix",},
+       {evalbuffer, "eval-current-buffer",},
+       {evalexpr, "eval-expression",},
+       {swapmark, "exchange-point-and-mark",},
+       {extend, "execute-extended-command",},
+       {fillpara, "fill-paragraph",},
+       {filevisit, "find-file",},
+       {filevisitro, "find-file-read-only",},
+       {filevisitalt, "find-alternate-file",},
+       {poptofile, "find-file-other-window",},
+       {forwchar, "forward-char",},
+       {gotoeop, "forward-paragraph",},
+       {forwword, "forward-word",},
+       {bindtokey, "global-set-key",},
+       {unbindtokey, "global-unset-key",},
+       {gotoline, "goto-line",},
+       {help_help, "help-help",},
+       {insert, "insert",},
+       {bufferinsert, "insert-buffer",},
+       {fileinsert, "insert-file",},
+       {fillword, "insert-with-wrap",},
+       {backisearch, "isearch-backward",},
+       {forwisearch, "isearch-forward",},
+       {joinline, "join-line",},
+       {justone, "just-one-space",},
+       {ctrlg, "keyboard-quit",},
+       {killbuffer_cmd, "kill-buffer",},
+       {killline, "kill-line",},
+       {killpara, "kill-paragraph",},
+       {killregion, "kill-region",},
+       {delfword, "kill-word",},
+       {toggleleavetmp, "leave-tmpdir-backups",},
+       {linenotoggle, "line-number-mode",},
+       {listbuffers, "list-buffers",},
+       {evalfile, "load",},
+       {localbind, "local-set-key",},
+       {localunbind, "local-unset-key",},
+       {makebkfile, "make-backup-files",},
+       {markbuffer, "mark-whole-buffer",},
+       {do_meta, "meta-key-mode",},    /* better name, anyone? */
+       {negative_argument, "negative-argument",},
+       {newline, "newline",},
+       {lfindent, "newline-and-indent",},
+       {indent, "indent-current-line",},
+       {forwline, "next-line",},
+#ifdef NOTAB
+       {notabmode, "no-tab-mode",},
+#endif /* NOTAB */
+       {notmodified, "not-modified",},
+       {openline, "open-line",},
+       {nextwind, "other-window",},
+       {overwrite_mode, "overwrite-mode",},
+       {prefixregion, "prefix-region",},
+       {backline, "previous-line",},
+       {prevwind, "previous-window",},
+       {poptag, "pop-tag-mark",},
+       {spawncli, "push-shell",},
+       {findtag, "find-tag",},
+       {tagsvisit, "visit-tags-table",},
+       {showcwdir, "pwd",},
+       {queryrepl, "query-replace",},
+#ifdef REGEX
+       {replstr, "replace-string",},
+       {re_queryrepl, "query-replace-regexp",},
+#endif /* REGEX */
+       {quote, "quoted-insert",},
+#ifdef REGEX
+       {re_searchagain, "re-search-again",},
+       {re_backsearch, "re-search-backward",},
+       {re_forwsearch, "re-search-forward",},
+#endif /* REGEX */
+       {reposition, "recenter",},
+       {redraw, "redraw-display",},
+       {filesave, "save-buffer",},
+       {quit, "save-buffers-kill-emacs",},
+       {savebuffers, "save-some-buffers",},
+       {backpage, "scroll-down",},
+       {back1page, "scroll-one-line-down",},
+       {forw1page, "scroll-one-line-up",},
+       {pagenext, "scroll-other-window",},
+       {forwpage, "scroll-up",},
+       {searchagain, "search-again",},
+       {backsearch, "search-backward",},
+       {forwsearch, "search-forward",},
+       {selfinsert, "self-insert-command",},
+#ifdef REGEX
+       {setcasefold, "set-case-fold-search",},
+#endif /* REGEX */
+       {set_default_mode, "set-default-mode",},
+       {setfillcol, "set-fill-column",},
+       {setmark, "set-mark-command",},
+       {setprefix, "set-prefix-string",},
+       {piperegion, "shell-command-on-region",},
+       {shrinkwind, "shrink-window",},
+#ifdef NOTAB
+       {space_to_tabstop, "space-to-tabstop",},
+#endif /* NOTAB */
+       {splitwind, "split-window-vertically",},
+       {definemacro, "start-kbd-macro",},
+       {spawncli, "suspend-emacs",},
+       {usebuffer, "switch-to-buffer",},
+       {poptobuffer, "switch-to-buffer-other-window",},
+       {togglereadonly, "toggle-read-only" },
+       {twiddle, "transpose-chars",},
+       {undo, "undo", },
+       {undo_enable, "undo-enable", },
+       {undo_boundary_enable, "undo-boundary-toggle", },
+       {undo_add_boundary, "undo-boundary", },
+       {undo_dump, "undo-list", },
+       {universal_argument, "universal-argument",},
+       {upperregion, "upcase-region",},
+       {upperword, "upcase-word",},
+       {showcpos, "what-cursor-position",},
+       {filewrite, "write-file",},
+       {yank, "yank",},
+       {cssymbol, "cscope-find-this-symbol",},
+       {csdefinition, "cscope-find-global-definition",},
+       {csfuncalled, "cscope-find-called-functions",},
+       {cscallerfuncs, "cscope-find-functions-calling-this-function",},
+       {csfindtext, "cscope-find-this-text-string",},
+       {csegrep, "cscope-find-egrep-pattern",},
+       {csfindfile, "cscope-find-this-file",},
+       {csfindinc, "cscope-find-files-including-file",},
+       {csnextmatch, "cscope-next-symbol",},
+       {csprevmatch, "cscope-prev-symbol",},
+       {csnextfile, "cscope-next-file",},
+       {csprevfile, "cscope-prev-file",},
+       {cscreatelist, "cscope-create-list-of-files-to-index"},
+       {NULL, NULL,}
+};
+
+void
+funmap_init(void)
+{
+       struct funmap *fn;
+
+       for (fn = functnames; fn->fn_name != NULL; fn++) {
+               fn->fn_next = funs;
+               funs = fn;
+       }
+}
+
+int
+funmap_add(PF fun, const char *fname)
+{
+       struct funmap *fn;
+
+       if ((fn = malloc(sizeof(*fn))) == NULL)
+               return (FALSE);
+
+       fn->fn_funct = fun;
+       fn->fn_name = fname;
+       fn->fn_next = funs;
+
+       funs = fn;
+       return (TRUE);
+}
+
+/*
+ * Translate from function name to function pointer.
+ */
+PF
+name_function(const char *fname)
+{
+       struct funmap *fn;
+
+       for (fn = funs; fn != NULL; fn = fn->fn_next) {
+               if (strcmp(fn->fn_name, fname) == 0)
+                       return (fn->fn_funct);
+       }
+       return (NULL);
+}
+
+const char *
+function_name(PF fun)
+{
+       struct funmap *fn;
+
+       for (fn = funs; fn != NULL; fn = fn->fn_next) {
+               if (fn->fn_funct == fun)
+                       return (fn->fn_name);
+       }
+       return (NULL);
+}
+
+/*
+ * List possible function name completions.
+ */
+struct list *
+complete_function_list(const char *fname)
+{
+       struct funmap   *fn;
+       struct list     *head, *el;
+       int              len;
+
+       len = strlen(fname);
+       head = NULL;
+       for (fn = funs; fn != NULL; fn = fn->fn_next) {
+               if (memcmp(fname, fn->fn_name, len) == 0) {
+                       if ((el = malloc(sizeof(*el))) == NULL) {
+                               free_file_list(head);
+                               return (NULL);
+                       }
+                       el->l_name = strdup(fn->fn_name);
+                       el->l_next = head;
+                       head = el;
+               }
+       }
+       return (head);
+}
diff --git a/mg/funmap.h b/mg/funmap.h
new file mode 100644 (file)
index 0000000..acc24ae
--- /dev/null
@@ -0,0 +1,9 @@
+/*     $OpenBSD: funmap.h,v 1.7 2008/06/10 00:19:31 kjell Exp $        */
+
+/* This file is in the public domain */
+
+void            funmap_init(void);
+PF              name_function(const char *);
+const char     *function_name(PF);
+struct list    *complete_function_list(const char *);
+int             funmap_add(PF, const char *);
diff --git a/mg/grep.c b/mg/grep.c
new file mode 100644 (file)
index 0000000..0d688b5
--- /dev/null
+++ b/mg/grep.c
@@ -0,0 +1,365 @@
+/*     $OpenBSD: grep.c,v 1.38 2009/06/04 23:39:37 kjell Exp $ */
+
+/* This file is in the public domain */
+
+#include "def.h"
+#include "kbd.h"
+#include "funmap.h"
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <libgen.h>
+#include <time.h>
+
+int     globalwd = FALSE;
+static int      compile_goto_error(int, int);
+int             next_error(int, int);
+static int      grep(int, int);
+static int      gid(int, int);
+static struct buffer   *compile_mode(const char *, const char *);
+static int      xlint(int, int);
+void grep_init(void);
+
+static char compile_last_command[NFILEN] = "make ";
+
+/*
+ * Hints for next-error
+ *
+ * XXX - need some kind of callback to find out when those get killed.
+ */
+struct mgwin   *compile_win;
+struct buffer  *compile_buffer;
+
+static PF compile_pf[] = {
+       compile_goto_error
+};
+
+static struct KEYMAPE (1 + IMAPEXT) compilemap = {
+       1,
+       1 + IMAPEXT,
+       rescan,
+       {
+               { CCHR('M'), CCHR('M'), compile_pf, NULL }
+       }
+};
+
+void
+grep_init(void)
+{
+       funmap_add(compile_goto_error, "compile-goto-error");
+       funmap_add(next_error, "next-error");
+       funmap_add(grep, "grep");
+       funmap_add(xlint, "lint");
+       funmap_add(compile, "compile");
+       funmap_add(gid, "gid");
+       maps_add((KEYMAP *)&compilemap, "compile");
+}
+
+/* ARGSUSED */
+static int
+grep(int f, int n)
+{
+       char     cprompt[NFILEN], *bufp;
+       struct buffer   *bp;
+       struct mgwin    *wp;
+
+       (void)strlcpy(cprompt, "grep -n ", sizeof(cprompt));
+       if ((bufp = eread("Run grep: ", cprompt, NFILEN,
+           EFDEF | EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       if (strlcat(cprompt, " /dev/null", sizeof(cprompt)) >= sizeof(cprompt))
+               return (FALSE);
+
+       if ((bp = compile_mode("*grep*", cprompt)) == NULL)
+               return (FALSE);
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       compile_win = curwp = wp;
+       return (TRUE);
+}
+
+/* ARGSUSED */
+static int
+xlint(int f, int n)
+{
+       char     cprompt[NFILEN], *bufp;
+       struct buffer   *bp;
+       struct mgwin    *wp;
+
+       (void)strlcpy(cprompt, "make lint ", sizeof(cprompt));
+       if ((bufp = eread("Run lint: ", cprompt, NFILEN,
+           EFDEF | EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+
+       if ((bp = compile_mode("*lint*", cprompt)) == NULL)
+               return (FALSE);
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       compile_win = curwp = wp;
+       return (TRUE);
+}
+
+/* ARGSUSED */
+int
+compile(int f, int n)
+{
+       char     cprompt[NFILEN], *bufp;
+       struct buffer   *bp;
+       struct mgwin    *wp;
+
+       (void)strlcpy(cprompt, compile_last_command, sizeof(cprompt));
+       if ((bufp = eread("Compile command: ", cprompt, NFILEN,
+           EFDEF | EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       if (savebuffers(f, n) == ABORT)
+               return (ABORT);
+       (void)strlcpy(compile_last_command, bufp, sizeof(compile_last_command));
+
+       if ((bp = compile_mode("*compile*", cprompt)) == NULL)
+               return (FALSE);
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       compile_win = curwp = wp;
+       gotoline(FFARG, 0);
+       return (TRUE);
+}
+
+/* id-utils foo. */
+/* ARGSUSED */
+static int
+gid(int f, int n)
+{
+       char     command[NFILEN];
+       char     cprompt[NFILEN], c, *bufp;
+       struct buffer   *bp;
+       struct mgwin    *wp;
+       int      i, j, len;
+
+       /* catch ([^\s(){}]+)[\s(){}]* */
+
+       i = curwp->w_doto;
+       /* Skip backwards over delimiters we are currently on */
+       while (i > 0) {
+               c = lgetc(curwp->w_dotp, i);
+               if (isalnum(c) || c == '_')
+                       break;
+
+               i--;
+       }
+
+       /* Skip the symbol itself */
+       for (; i > 0; i--) {
+               c = lgetc(curwp->w_dotp, i - 1);
+               if (!isalnum(c) && c != '_')
+                       break;
+       }
+       /* Fill the symbol in cprompt[] */
+       for (j = 0; j < sizeof(cprompt) - 1 && i < llength(curwp->w_dotp);
+           j++, i++) {
+               c = lgetc(curwp->w_dotp, i);
+               if (!isalnum(c) && c != '_')
+                       break;
+               cprompt[j] = c;
+       }
+       cprompt[j] = '\0';
+
+       if ((bufp = eread("Run gid (with args): ", cprompt, NFILEN,
+           (j ? EFDEF : 0) | EFNEW | EFCR)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       len = snprintf(command, sizeof(command), "gid %s", cprompt);
+       if (len < 0 || len >= sizeof(command))
+               return (FALSE);
+
+       if ((bp = compile_mode("*gid*", command)) == NULL)
+               return (FALSE);
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       compile_win = curwp = wp;
+       return (TRUE);
+}
+
+struct buffer *
+compile_mode(const char *name, const char *command)
+{
+       struct buffer   *bp;
+       FILE    *fpipe;
+       char    *buf;
+       size_t   len;
+       int      ret, n;
+       char     cwd[NFILEN], qcmd[NFILEN];
+       char     timestr[NTIME];
+       time_t   t;
+
+       n = snprintf(qcmd, sizeof(qcmd), "%s 2>&1", command);
+       if (n < 0 || n >= sizeof(qcmd))
+               return (NULL);
+
+       bp = bfind(name, TRUE);
+       if (bclear(bp) != TRUE)
+               return (NULL);
+
+       if (getbufcwd(bp->b_cwd, sizeof(bp->b_cwd)) != TRUE)
+               return (NULL);
+       addlinef(bp, "cd %s", bp->b_cwd);
+       addline(bp, qcmd);
+       addline(bp, "");
+
+       if (getcwd(cwd, sizeof(cwd)) == NULL)
+               panic("Can't get current directory!");
+       if (chdir(bp->b_cwd) == -1) {
+               ewprintf("Can't change dir to %s", bp->b_cwd);
+               return (NULL);
+       }
+       if ((fpipe = popen(qcmd, "r")) == NULL) {
+               ewprintf("Problem opening pipe");
+               return (NULL);
+       }
+       /*
+        * We know that our commands are nice and the last line will end with
+        * a \n, so we don't need to try to deal with the last line problem
+        * in fgetln.
+        */
+       while ((buf = fgetln(fpipe, &len)) != NULL) {
+               buf[len - 1] = '\0';
+               addline(bp, buf);
+       }
+       ret = pclose(fpipe);
+       t = time(NULL);
+       strftime(timestr, sizeof(timestr), "%a %b %e %T %Y", localtime(&t));
+       addline(bp, "");
+       if (ret != 0)
+               addlinef(bp, "Command exited abnormally with code %d"
+                   " at %s", ret, timestr);
+       else
+               addlinef(bp, "Command finished at %s", timestr);
+
+       bp->b_dotp = bfirstlp(bp);
+       bp->b_modes[0] = name_mode("fundamental");
+       bp->b_modes[1] = name_mode("compile");
+       bp->b_nmodes = 1;
+
+       compile_buffer = bp;
+
+       if (chdir(cwd) == -1) {
+               ewprintf("Can't change dir back to %s", cwd);
+               return (NULL);
+       }
+       return (bp);
+}
+
+/* ARGSUSED */
+static int
+compile_goto_error(int f, int n)
+{
+       struct buffer   *bp;
+       struct mgwin    *wp;
+       char    *fname, *line, *lp, *ln;
+       int      lineno;
+       char    *adjf, path[NFILEN];
+       const char *errstr;
+       struct line     *last;
+
+       compile_win = curwp;
+       compile_buffer = curbp;
+       last = blastlp(compile_buffer);
+
+ retry:
+       /* last line is compilation result */
+       if (curwp->w_dotp == last)
+               return (FALSE);
+
+       if ((line = linetostr(curwp->w_dotp)) == NULL)
+               return (FALSE);
+       lp = line;
+       if ((fname = strsep(&lp, ":")) == NULL || *fname == '\0')
+               goto fail;
+       if ((ln = strsep(&lp, ":")) == NULL || *ln == '\0')
+               goto fail;
+       lineno = (int)strtonum(ln, INT_MIN, INT_MAX, &errstr);
+       if (errstr)
+               goto fail;
+
+       if (fname && fname[0] != '/') {
+               if (getbufcwd(path, sizeof(path)) == FALSE)
+                       goto fail;
+               if (strlcat(path, fname, sizeof(path)) >= sizeof(path))
+                       goto fail;
+               adjf = path;
+       } else {
+               adjf = adjustname(fname, TRUE);
+       }
+       free(line);
+
+       if (adjf == NULL)
+               return (FALSE);
+
+       if ((bp = findbuffer(adjf)) == NULL)
+               return (FALSE);
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+       curbp = bp;
+       curwp = wp;
+       if (bp->b_fname[0] == '\0')
+               readin(adjf);
+       gotoline(FFARG, lineno);
+       return (TRUE);
+fail:
+       free(line);
+       if (curwp->w_dotp != blastlp(curbp)) {
+               curwp->w_dotp = lforw(curwp->w_dotp);
+               curwp->w_rflag |= WFMOVE;
+               goto retry;
+       }
+       ewprintf("No more hits");
+       return (FALSE);
+}
+
+/* ARGSUSED */
+int
+next_error(int f, int n)
+{
+       if (compile_win == NULL || compile_buffer == NULL) {
+               ewprintf("No compilation active");
+               return (FALSE);
+       }
+       curwp = compile_win;
+       curbp = compile_buffer;
+       if (curwp->w_dotp == blastlp(curbp)) {
+               ewprintf("No more hits");
+               return (FALSE);
+       }
+       curwp->w_dotp = lforw(curwp->w_dotp);
+       curwp->w_rflag |= WFMOVE;
+
+       return (compile_goto_error(f, n));
+}
+
+/*
+ * Since we don't have variables (we probably should) these are command
+ * processors for changing the values of mode flags.
+ */
+/* ARGSUSED */
+int
+globalwdtoggle(int f, int n)
+{
+       if (f & FFARG)
+               globalwd = n > 0;
+       else
+               globalwd = !globalwd;
+
+       sgarbf = TRUE;
+
+       return (TRUE);
+}
diff --git a/mg/help.c b/mg/help.c
new file mode 100644 (file)
index 0000000..938f813
--- /dev/null
+++ b/mg/help.c
@@ -0,0 +1,231 @@
+/*     $OpenBSD: help.c,v 1.34 2012/04/12 04:47:59 lum Exp $   */
+
+/* This file is in the public domain. */
+
+/*
+ * Help functions for Mg 2
+ */
+
+#include "def.h"
+#include "funmap.h"
+
+#include "kbd.h"
+#include "key.h"
+#include "macro.h"
+
+static int     showall(struct buffer *, KEYMAP *, char *);
+static int     findbind(KEYMAP *, PF, char *, size_t);
+
+/*
+ * Read a key from the keyboard, and look it up in the keymap.
+ * Display the name of the function currently bound to the key.
+ */
+/* ARGSUSED */
+int
+desckey(int f, int n)
+{
+       KEYMAP  *curmap;
+       PF       funct;
+       int      c, m, i, num;
+       char    *pep;
+       char     dprompt[80];
+
+       if (inmacro)
+               return (TRUE);  /* ignore inside keyboard macro */
+
+       num = strlcpy(dprompt, "Describe key briefly: ", sizeof(dprompt));
+       if (num >= sizeof(dprompt))
+               num = sizeof(dprompt) - 1;
+       pep = dprompt + num;
+       key.k_count = 0;
+       m = curbp->b_nmodes;
+       curmap = curbp->b_modes[m]->p_map;
+       for (;;) {
+               for (;;) {
+                       ewprintf("%s", dprompt);
+                       pep[-1] = ' ';
+                       pep = getkeyname(pep, sizeof(dprompt) - (pep - dprompt),
+                           key.k_chars[key.k_count++] = c = getkey(FALSE));
+                       if ((funct = doscan(curmap, c, &curmap)) != NULL)
+                               break;
+                       *pep++ = '-';
+                       *pep = '\0';
+               }
+               if (funct != rescan)
+                       break;
+               if (ISUPPER(key.k_chars[key.k_count - 1])) {
+                       funct = doscan(curmap,
+                           TOLOWER(key.k_chars[key.k_count - 1]), &curmap);
+                       if (funct == NULL) {
+                               *pep++ = '-';
+                               *pep = '\0';
+                               continue;
+                       }
+                       if (funct != rescan)
+                               break;
+               }
+nextmode:
+               if (--m < 0)
+                       break;
+               curmap = curbp->b_modes[m]->p_map;
+               for (i = 0; i < key.k_count; i++) {
+                       funct = doscan(curmap, key.k_chars[i], &curmap);
+                       if (funct != NULL) {
+                               if (i == key.k_count - 1 && funct != rescan)
+                                       goto found;
+                               funct = rescan;
+                               goto nextmode;
+                       }
+               }
+               *pep++ = '-';
+               *pep = '\0';
+       }
+found:
+       if (funct == rescan || funct == selfinsert)
+               ewprintf("%k is not bound to any function");
+       else if ((pep = (char *)function_name(funct)) != NULL)
+               ewprintf("%k runs the command %s", pep);
+       else
+               ewprintf("%k is bound to an unnamed function");
+       return (TRUE);
+}
+
+/*
+ * This function creates a table, listing all of the command
+ * keys and their current bindings, and stores the table in the
+ * *help* pop-up buffer.  This lets Mg produce it's own wall chart.
+ */
+/* ARGSUSED */
+int
+wallchart(int f, int n)
+{
+       int              m;
+       struct buffer           *bp;
+
+       bp = bfind("*help*", TRUE);
+       if (bclear(bp) != TRUE)
+               /* clear it out */
+               return (FALSE);
+       bp->b_flag |= BFREADONLY;
+       for (m = curbp->b_nmodes; m > 0; m--) {
+               if ((addlinef(bp, "Local keybindings for mode %s:",
+                               curbp->b_modes[m]->p_name) == FALSE) ||
+                   (showall(bp, curbp->b_modes[m]->p_map, "") == FALSE) ||
+                   (addline(bp, "") == FALSE))
+                       return (FALSE);
+       }
+       if ((addline(bp, "Global bindings:") == FALSE) ||
+           (showall(bp, fundamental_map, "") == FALSE))
+               return (FALSE);
+       return (popbuftop(bp, WNONE));
+}
+
+static int
+showall(struct buffer *bp, KEYMAP *map, char *prefix)
+{
+       KEYMAP  *newmap;
+       char     buf[80], keybuf[16];
+       PF       fun;
+       int      c;
+
+       if (addline(bp, "") == FALSE)
+               return (FALSE);
+
+       /* XXX - 256 ? */
+       for (c = 0; c < 256; c++) {
+               fun = doscan(map, c, &newmap);
+               if (fun == rescan || fun == selfinsert)
+                       continue;
+               getkeyname(buf, sizeof(buf), c);
+               (void)snprintf(keybuf, sizeof(keybuf), "%s%s ", prefix, buf);
+               if (fun == NULL) {
+                       if (showall(bp, newmap, keybuf) == FALSE)
+                               return (FALSE);
+               } else {
+                       if (addlinef(bp, "%-16s%s", keybuf,
+                                   function_name(fun)) == FALSE)
+                               return (FALSE);
+               }
+       }
+       return (TRUE);
+}
+
+int
+help_help(int f, int n)
+{
+       KEYMAP  *kp;
+       PF       funct;
+
+       if ((kp = name_map("help")) == NULL)
+               return (FALSE);
+       ewprintf("a b c: ");
+       do {
+               funct = doscan(kp, getkey(FALSE), NULL);
+       } while (funct == NULL || funct == help_help);
+
+       if (macrodef && macrocount < MAXMACRO)
+               macro[macrocount - 1].m_funct = funct;
+
+       return ((*funct)(f, n));
+}
+
+/* ARGSUSED */
+int
+apropos_command(int f, int n)
+{
+       struct buffer           *bp;
+       struct list             *fnames, *el;
+       char             string[32];
+
+       if (eread("apropos: ", string, sizeof(string), EFNUL | EFNEW) == NULL)
+               return (ABORT);
+       /* FALSE means we got a 0 character string, which is fine */
+       bp = bfind("*help*", TRUE);
+       if (bclear(bp) == FALSE)
+               return (FALSE);
+
+       fnames = complete_function_list("");
+       for (el = fnames; el != NULL; el = el->l_next) {
+               char buf[32];
+
+               if (strstr(el->l_name, string) == NULL)
+                       continue;
+
+               buf[0] = '\0';
+               findbind(fundamental_map, name_function(el->l_name),
+                   buf, sizeof(buf));
+
+               if (addlinef(bp, "%-32s%s", el->l_name,  buf) == FALSE) {
+                       free_file_list(fnames);
+                       return (FALSE);
+               }
+       }
+       free_file_list(fnames);
+       return (popbuftop(bp, WNONE));
+}
+
+static int
+findbind(KEYMAP *map, PF fun, char *buf, size_t len)
+{
+       KEYMAP  *newmap;
+       PF       nfun;
+       char     buf2[16], keybuf[16];
+       int      c;
+
+       /* XXX - 256 ? */
+       for (c = 0; c < 256; c++) {
+               nfun = doscan(map, c, &newmap);
+               if (nfun == fun) {
+                       getkeyname(buf, len, c);
+                       return (TRUE);
+               }
+               if (nfun == NULL) {
+                       if (findbind(newmap, fun, buf2, sizeof(buf2)) == TRUE) {
+                               getkeyname(keybuf, sizeof(keybuf), c);
+                               (void)snprintf(buf, len, "%s %s", keybuf, buf2);
+                               return (TRUE);
+                       }
+               }
+       }
+       return (FALSE);
+}
diff --git a/mg/kbd.c b/mg/kbd.c
new file mode 100644 (file)
index 0000000..0eb3c36
--- /dev/null
+++ b/mg/kbd.c
@@ -0,0 +1,436 @@
+/*     $OpenBSD: kbd.c,v 1.25 2012/04/12 04:47:59 lum Exp $    */
+
+/* This file is in the public domain. */
+
+/*
+ *     Terminal independent keyboard handling.
+ */
+
+#include "def.h"
+#include "kbd.h"
+#include "key.h"
+#include "macro.h"
+
+#ifndef METABIT
+#define METABIT 0x80
+#endif /* !METABIT */
+
+#ifndef NO_DPROMPT
+#define PROMPTL 80
+char    prompt[PROMPTL] = "", *promptp = prompt;
+#endif /* !NO_DPROMPT */
+
+static int mgwrap(PF, int, int);
+
+static int      use_metakey = TRUE;
+static int      pushed = FALSE;
+static int      pushedc;
+
+struct map_element     *ele;
+
+struct key key;
+
+/*
+ * Toggle the value of use_metakey
+ */
+int
+do_meta(int f, int n)
+{
+       if (f & FFARG)
+               use_metakey = n > 0;
+       else
+               use_metakey = !use_metakey;
+       ewprintf("Meta keys %sabled", use_metakey ? "en" : "dis");
+       return (TRUE);
+}
+
+static int      bs_map = 0;
+
+/*
+ * Toggle backspace mapping
+ */
+int
+bsmap(int f, int n)
+{
+       if (f & FFARG)
+               bs_map = n > 0;
+       else
+               bs_map = !bs_map;
+       ewprintf("Backspace mapping %sabled", bs_map ? "en" : "dis");
+       return (TRUE);
+}
+
+void
+ungetkey(int c)
+{
+       if (use_metakey && pushed && c == CCHR('['))
+               pushedc |= METABIT;
+       else
+               pushedc = c;
+       pushed = TRUE;
+}
+
+int
+getkey(int flag)
+{
+       int      c;
+
+#ifndef NO_DPROMPT
+       if (flag && !pushed) {
+               if (prompt[0] != '\0' && ttwait(2000)) {
+                       /* avoid problems with % */
+                       ewprintf("%s", prompt);
+                       /* put the cursor back */
+                       update();
+                       epresf = KCLEAR;
+               }
+               if (promptp > prompt)
+                       *(promptp - 1) = ' ';
+       }
+#endif /* !NO_DPROMPT */
+       if (pushed) {
+               c = pushedc;
+               pushed = FALSE;
+       } else
+               c = ttgetc();
+
+       if (bs_map) {
+               if (c == CCHR('H'))
+                       c = CCHR('?');
+               else if (c == CCHR('?'))
+                       c = CCHR('H');
+       }
+       if (use_metakey && (c & METABIT)) {
+               pushedc = c & ~METABIT;
+               pushed = TRUE;
+               c = CCHR('[');
+       }
+#ifndef NO_DPROMPT
+       if (flag && promptp < &prompt[PROMPTL - 5]) {
+               promptp = getkeyname(promptp,
+                   sizeof(prompt) - (promptp - prompt) - 1, c);
+               *promptp++ = '-';
+               *promptp = '\0';
+       }
+#endif /* !NO_DPROMPT */
+       return (c);
+}
+
+/*
+ * doscan scans a keymap for a keyboard character and returns a pointer
+ * to the function associated with that character.  Sets ele to the
+ * keymap element the keyboard was found in as a side effect.
+ */
+PF
+doscan(KEYMAP *map, int c, KEYMAP **newmap)
+{
+       struct map_element      *elec = &map->map_element[0];
+       struct map_element      *last = &map->map_element[map->map_num];
+       PF               ret;
+
+       while (elec < last && c > elec->k_num)
+               elec++;
+
+       /* used by prefix and binding code */
+       ele = elec;
+       if (elec >= last || c < elec->k_base)
+               ret = map->map_default;
+       else
+               ret = elec->k_funcp[c - elec->k_base];
+       if (ret == NULL && newmap != NULL)
+               *newmap = elec->k_prefmap;
+
+       return (ret);
+}
+
+int
+doin(void)
+{
+       KEYMAP  *curmap;
+       PF       funct;
+
+#ifndef NO_DPROMPT
+       *(promptp = prompt) = '\0';
+#endif /* !NO_DPROMPT */
+       curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
+       key.k_count = 0;
+       while ((funct = doscan(curmap, (key.k_chars[key.k_count++] =
+           getkey(TRUE)), &curmap)) == NULL)
+               /* nothing */;
+
+       if (macrodef && macrocount < MAXMACRO)
+               macro[macrocount++].m_funct = funct;
+
+       return (mgwrap(funct, 0, 1));
+}
+
+int
+rescan(int f, int n)
+{
+       int      c;
+       KEYMAP  *curmap;
+       int      i;
+       PF       fp = NULL;
+       int      md = curbp->b_nmodes;
+
+       for (;;) {
+               if (ISUPPER(key.k_chars[key.k_count - 1])) {
+                       c = TOLOWER(key.k_chars[key.k_count - 1]);
+                       curmap = curbp->b_modes[md]->p_map;
+                       for (i = 0; i < key.k_count - 1; i++) {
+                               if ((fp = doscan(curmap, (key.k_chars[i]),
+                                   &curmap)) != NULL)
+                                       break;
+                       }
+                       if (fp == NULL) {
+                               if ((fp = doscan(curmap, c, NULL)) == NULL)
+                                       while ((fp = doscan(curmap,
+                                           key.k_chars[key.k_count++] =
+                                           getkey(TRUE), &curmap)) == NULL)
+                                               /* nothing */;
+                               if (fp != rescan) {
+                                       if (macrodef && macrocount <= MAXMACRO)
+                                               macro[macrocount - 1].m_funct
+                                                   = fp;
+                                       return (mgwrap(fp, f, n));
+                               }
+                       }
+               }
+               /* try previous mode */
+               if (--md < 0)
+                       return (ABORT);
+               curmap = curbp->b_modes[md]->p_map;
+               for (i = 0; i < key.k_count; i++) {
+                       if ((fp = doscan(curmap, (key.k_chars[i]), &curmap)) != NULL)
+                               break;
+               }
+               if (fp == NULL) {
+                       while ((fp = doscan(curmap, key.k_chars[i++] =
+                           getkey(TRUE), &curmap)) == NULL)
+                               /* nothing */;
+                       key.k_count = i;
+               }
+               if (fp != rescan && i >= key.k_count - 1) {
+                       if (macrodef && macrocount <= MAXMACRO)
+                               macro[macrocount - 1].m_funct = fp;
+                       return (mgwrap(fp, f, n));
+               }
+       }
+}
+
+int
+universal_argument(int f, int n)
+{
+       KEYMAP  *curmap;
+       PF       funct;
+       int      c, nn = 4;
+
+       if (f & FFUNIV)
+               nn *= n;
+       for (;;) {
+               key.k_chars[0] = c = getkey(TRUE);
+               key.k_count = 1;
+               if (c == '-')
+                       return (negative_argument(f, nn));
+               if (c >= '0' && c <= '9')
+                       return (digit_argument(f, nn));
+               curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
+               while ((funct = doscan(curmap, c, &curmap)) == NULL) {
+                       key.k_chars[key.k_count++] = c = getkey(TRUE);
+               }
+               if (funct != universal_argument) {
+                       if (macrodef && macrocount < MAXMACRO - 1) {
+                               if (f & FFARG)
+                                       macrocount--;
+                               macro[macrocount++].m_count = nn;
+                               macro[macrocount++].m_funct = funct;
+                       }
+                       return (mgwrap(funct, FFUNIV, nn));
+               }
+               nn <<= 2;
+       }
+}
+
+/* ARGSUSED */
+int
+digit_argument(int f, int n)
+{
+       KEYMAP  *curmap;
+       PF       funct;
+       int      nn, c;
+
+       nn = key.k_chars[key.k_count - 1] - '0';
+       for (;;) {
+               c = getkey(TRUE);
+               if (c < '0' || c > '9')
+                       break;
+               nn *= 10;
+               nn += c - '0';
+       }
+       key.k_chars[0] = c;
+       key.k_count = 1;
+       curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
+       while ((funct = doscan(curmap, c, &curmap)) == NULL) {
+               key.k_chars[key.k_count++] = c = getkey(TRUE);
+       }
+       if (macrodef && macrocount < MAXMACRO - 1) {
+               if (f & FFARG)
+                       macrocount--;
+               else
+                       macro[macrocount - 1].m_funct = universal_argument;
+               macro[macrocount++].m_count = nn;
+               macro[macrocount++].m_funct = funct;
+       }
+       return (mgwrap(funct, FFOTHARG, nn));
+}
+
+int
+negative_argument(int f, int n)
+{
+       KEYMAP  *curmap;
+       PF       funct;
+       int      c;
+       int      nn = 0;
+
+       for (;;) {
+               c = getkey(TRUE);
+               if (c < '0' || c > '9')
+                       break;
+               nn *= 10;
+               nn += c - '0';
+       }
+       if (nn)
+               nn = -nn;
+       else
+               nn = -n;
+       key.k_chars[0] = c;
+       key.k_count = 1;
+       curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
+       while ((funct = doscan(curmap, c, &curmap)) == NULL) {
+               key.k_chars[key.k_count++] = c = getkey(TRUE);
+       }
+       if (macrodef && macrocount < MAXMACRO - 1) {
+               if (f & FFARG)
+                       macrocount--;
+               else
+                       macro[macrocount - 1].m_funct = universal_argument;
+               macro[macrocount++].m_count = nn;
+               macro[macrocount++].m_funct = funct;
+       }
+       return (mgwrap(funct, FFNEGARG, nn));
+}
+
+/*
+ * Insert a character. While defining a macro, create a "LINE" containing
+ * all inserted characters.
+ */
+int
+selfinsert(int f, int n)
+{
+       struct line     *lp;
+       int      c;
+       int      count;
+
+       if (n < 0)
+               return (FALSE);
+       if (n == 0)
+               return (TRUE);
+       c = key.k_chars[key.k_count - 1];
+
+       if (macrodef && macrocount < MAXMACRO) {
+               if (f & FFARG)
+                       macrocount -= 2;
+
+               /* last command was insert -- tack on the end */
+               if (lastflag & CFINS) {
+                       macrocount--;
+                       /* Ensure the line can handle the new characters */
+                       if (maclcur->l_size < maclcur->l_used + n) {
+                               if (lrealloc(maclcur, maclcur->l_used + n) ==
+                                   FALSE)
+                                       return (FALSE);
+                       }
+                       maclcur->l_used += n;
+                       /* Copy in the new data */
+                       for (count = maclcur->l_used - n;
+                           count < maclcur->l_used; count++)
+                               maclcur->l_text[count] = c;
+               } else {
+                       macro[macrocount - 1].m_funct = insert;
+                       if ((lp = lalloc(n)) == NULL)
+                               return (FALSE);
+                       lp->l_bp = maclcur;
+                       lp->l_fp = maclcur->l_fp;
+                       maclcur->l_fp = lp;
+                       maclcur = lp;
+                       for (count = 0; count < n; count++)
+                               lp->l_text[count] = c;
+               }
+               thisflag |= CFINS;
+       }
+       if (c == '\n') {
+               do {
+                       count = lnewline();
+               } while (--n && count == TRUE);
+               return (count);
+       }
+
+       /* overwrite mode */
+       if (curbp->b_flag & BFOVERWRITE) {
+               lchange(WFEDIT);
+               while (curwp->w_doto < llength(curwp->w_dotp) && n--)
+                       lputc(curwp->w_dotp, curwp->w_doto++, c);
+               if (n <= 0)
+                       return (TRUE);
+       }
+       return (linsert(n, c));
+}
+
+/*
+ * This could be implemented as a keymap with everything defined as self-insert.
+ */
+int
+quote(int f, int n)
+{
+       int      c;
+
+       key.k_count = 1;
+       if ((key.k_chars[0] = getkey(TRUE)) >= '0' && key.k_chars[0] <= '7') {
+               key.k_chars[0] -= '0';
+               if ((c = getkey(TRUE)) >= '0' && c <= '7') {
+                       key.k_chars[0] <<= 3;
+                       key.k_chars[0] += c - '0';
+                       if ((c = getkey(TRUE)) >= '0' && c <= '7') {
+                               key.k_chars[0] <<= 3;
+                               key.k_chars[0] += c - '0';
+                       } else
+                               ungetkey(c);
+               } else
+                       ungetkey(c);
+       }
+       return (selfinsert(f, n));
+}
+
+/*
+ * Wraper function to count invocation repeats.
+ * We ignore any function whose sole purpose is to get us
+ * to the intended function.
+ */
+static int
+mgwrap(PF funct, int f, int n)
+{
+       static   PF ofp;
+       
+       if (funct != rescan &&
+           funct != negative_argument &&
+           funct != digit_argument &&
+           funct != universal_argument) {
+               if (funct == ofp)
+                       rptcount++;
+               else
+                       rptcount = 0;
+               ofp = funct;
+       }
+       
+       return ((*funct)(f, n));
+}
diff --git a/mg/kbd.h b/mg/kbd.h
new file mode 100644 (file)
index 0000000..67b244c
--- /dev/null
+++ b/mg/kbd.h
@@ -0,0 +1,58 @@
+/*     $OpenBSD: kbd.h,v 1.18 2006/07/27 19:59:29 deraadt Exp $        */
+
+/* This file is in the public domain. */
+
+/*
+ * kbd.h: type definitions for symbol.c and kbd.c for mg experimental
+ */
+
+struct map_element {
+       KCHAR            k_base;        /* first key in element          */
+       KCHAR            k_num;         /* last key in element           */
+       PF              *k_funcp;       /* pointer to array of pointers  */
+                                       /* to functions                  */
+       struct keymap_s *k_prefmap;     /* keymap of ONLY prefix key in  */
+                                       /* element                       */
+};
+
+/*
+ * Predefined keymaps are NOT type KEYMAP because final array needs
+ * dimension.  If any changes are made to this struct, they must be reflected
+ * in all keymap declarations.
+ */
+
+#define KEYMAPE(NUM)   {                                               \
+       short   map_num;                        /* elements used */     \
+       short   map_max;                        /* elements allocated */\
+       PF      map_default;                    /* default function */  \
+       struct map_element map_element[NUM];    /* really [e_max] */    \
+}
+typedef struct keymap_s KEYMAPE(1) KEYMAP;
+
+/* Number of map_elements to grow an overflowed keymap by */
+#define IMAPEXT 0
+#define MAPGROW 3
+#define MAPINIT (MAPGROW+1)
+
+/* Max number of default bindings added to avoid creating new element */
+#define MAPELEDEF 4
+
+struct maps_s {
+       KEYMAP          *p_map;
+       const char      *p_name;
+       struct maps_s   *p_next;
+};
+
+extern struct maps_s   *maps;
+extern struct maps_s   fundamental_mode;
+#define                fundamental_map (fundamental_mode.p_map)
+
+int             dobindkey(KEYMAP *, const char *, const char *);
+KEYMAP         *name_map(const char *);
+struct maps_s  *name_mode(const char *);
+PF              doscan(KEYMAP *, int, KEYMAP **);
+void            maps_init(void);
+int             maps_add(KEYMAP *, const char *);
+
+extern struct map_element      *ele;
+extern struct maps_s           *defb_modes[];
diff --git a/mg/key.h b/mg/key.h
new file mode 100644 (file)
index 0000000..3b2a7cf
--- /dev/null
+++ b/mg/key.h
@@ -0,0 +1,14 @@
+/*     $OpenBSD: key.h,v 1.5 2005/06/14 18:14:40 kjell Exp $   */
+
+/* This file is in the public domain. */
+
+/* key.h: Insert file for mg 2 functions that need to reference key pressed */
+
+#define MAXKEY 8                       /* maximum number of prefix chars */
+
+struct key {                           /* the chacter sequence in a key */
+       int     k_count;                /* number of chars */
+       KCHAR   k_chars[MAXKEY];        /* chars */
+};
+
+extern struct key key;
diff --git a/mg/keymap.c b/mg/keymap.c
new file mode 100644 (file)
index 0000000..94aead6
--- /dev/null
@@ -0,0 +1,567 @@
+/*     $OpenBSD: keymap.c,v 1.50 2012/06/07 15:15:04 lum Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ * Keyboard maps.  This is character set dependent.  The terminal specific
+ * parts of building the keymap has been moved to a better place.
+ */
+
+#include "def.h"
+#include "kbd.h"
+
+/*
+ * initial keymap declarations, deepest first
+ */
+
+static PF cHcG[] = {
+       ctrlg,                  /* ^G */
+       help_help               /* ^H */
+};
+
+static PF cHa[] = {
+       apropos_command,        /* a */
+       wallchart,              /* b */
+       desckey                 /* c */
+};
+
+struct KEYMAPE (2 + IMAPEXT) helpmap = {
+       2,
+       2 + IMAPEXT,
+       rescan,
+       {
+               {
+                       CCHR('G'), CCHR('H'), cHcG, NULL
+               },
+               {
+                       'a', 'c', cHa, NULL
+               }
+       }
+};
+
+static PF cCsc[] = {
+       cscallerfuncs,          /* c */
+       csdefinition,           /* d */
+       csegrep,                /* e */
+       csfindfile,             /* f */
+       rescan,                 /* g */
+       rescan,                 /* h */
+       csfindinc,              /* i */
+       rescan,                 /* j */
+       rescan,                 /* k */
+       rescan,                 /* l */
+       rescan,                 /* m */
+       csnextmatch,            /* n */
+       rescan,                 /* o */
+       csprevmatch,            /* p */
+       rescan,                 /* q */
+       rescan,                 /* r */ 
+       cssymbol,               /* s */
+       csfindtext              /* t */
+};
+
+static struct KEYMAPE (1 + IMAPEXT) cCsmap = {
+       1,
+       1 + IMAPEXT,
+       rescan,
+       {
+               {
+                       'c', 't', cCsc, NULL
+               }
+       }
+};
+
+static PF cCs[] = {
+       NULL                    /* s */
+};
+
+struct KEYMAPE (2 + IMAPEXT) ccmap = {
+       2,
+       2 + IMAPEXT,
+       rescan,
+       {
+               {
+                       CCHR('@'), CCHR('@'), (PF[]){ rescan }, NULL
+               },
+               {
+                       's', 's', cCs, (KEYMAP *) & cCsmap
+               }
+       }
+};
+
+static PF cX4cF[] = {
+       poptofile,              /* ^f */
+       ctrlg                   /* ^g */
+};
+static PF cX4b[] = {
+       poptobuffer,            /* b */
+       rescan,                 /* c */
+       rescan,                 /* d */
+       rescan,                 /* e */
+       poptofile               /* f */
+};
+static struct KEYMAPE (2 + IMAPEXT) cX4map = {
+       2,
+       2 + IMAPEXT,
+       rescan,
+       {
+               {
+                       CCHR('F'), CCHR('G'), cX4cF, NULL
+               },
+               {
+                       'b', 'f', cX4b, NULL
+               }
+       }
+};
+
+static PF cXcB[] = {
+       listbuffers,            /* ^B */
+       quit,                   /* ^C */
+       rescan,                 /* ^D */
+       rescan,                 /* ^E */
+       filevisit,              /* ^F */
+       ctrlg                   /* ^G */
+};
+
+static PF cXcL[] = {
+       lowerregion,            /* ^L */
+       rescan,                 /* ^M */
+       rescan,                 /* ^N */
+       deblank,                /* ^O */
+       rescan,                 /* ^P */
+       togglereadonly,         /* ^Q */
+       filevisitro,            /* ^R */
+       filesave,               /* ^S */
+       rescan,                 /* ^T */
+       upperregion,            /* ^U */
+       filevisitalt,           /* ^V */
+       filewrite,              /* ^W */
+       swapmark                /* ^X */
+};
+
+static PF cXlp[] = {
+       definemacro,            /* ( */
+       finishmacro             /* ) */
+};
+
+static PF cX0[] = {
+       delwind,                /* 0 */
+       onlywind,               /* 1 */
+       splitwind,              /* 2 */
+       rescan,                 /* 3 */
+       NULL                    /* 4 */
+};
+
+static PF cXeq[] = {
+       showcpos                /* = */
+};
+
+static PF cXcar[] = {
+       enlargewind,            /* ^ */
+       rescan,                 /* _ */
+       next_error,             /* ` */
+       rescan,                 /* a */
+       usebuffer,              /* b */
+       rescan,                 /* c */
+       rescan,                 /* d */
+       executemacro,           /* e */
+       setfillcol,             /* f */
+       gotoline,               /* g */
+       markbuffer,             /* h */
+       fileinsert,             /* i */
+       rescan,                 /* j */
+       killbuffer_cmd,         /* k */
+       rescan,                 /* l */
+       rescan,                 /* m */
+       nextwind,               /* n */
+       nextwind,               /* o */
+       prevwind,               /* p */
+       rescan,                 /* q */
+       rescan,                 /* r */
+       savebuffers,            /* s */
+       rescan,                 /* t */
+       undo                    /* u */
+};
+
+struct KEYMAPE (6 + IMAPEXT) cXmap = {
+       6,
+       6 + IMAPEXT,
+       rescan,
+       {
+               {
+                       CCHR('B'), CCHR('G'), cXcB, NULL
+               },
+               {
+                       CCHR('L'), CCHR('X'), cXcL, NULL
+               },
+               {
+                       '(', ')', cXlp, NULL
+               },
+               {
+                       '0', '4', cX0, (KEYMAP *) & cX4map
+               },
+               {
+                       '=', '=', cXeq, NULL
+               },
+               {
+                       '^', 'u', cXcar, NULL
+               }
+       }
+};
+
+static PF metacG[] = {
+       ctrlg                   /* ^G */
+};
+
+static PF metacV[] = {
+       pagenext                /* ^V */
+};
+
+static PF metasp[] = {
+       justone                 /* space */
+};
+
+static PF metapct[] = {
+       queryrepl               /* % */
+};
+
+static PF metami[] = {
+       poptag,                 /* * */
+       rescan,                 /* + */
+       rescan,                 /* , */
+       negative_argument,      /* - */
+       findtag,                /* . */
+       rescan,                 /* / */
+       digit_argument,         /* 0 */
+       digit_argument,         /* 1 */
+       digit_argument,         /* 2 */
+       digit_argument,         /* 3 */
+       digit_argument,         /* 4 */
+       digit_argument,         /* 5 */
+       digit_argument,         /* 6 */
+       digit_argument,         /* 7 */
+       digit_argument,         /* 8 */
+       digit_argument,         /* 9 */
+       rescan,                 /* : */
+       rescan,                 /* ; */
+       gotobob,                /* < */
+       rescan,                 /* = */
+       gotoeob                 /* > */
+};
+
+static PF metasqf[] = {
+       NULL,                   /* [ */
+       delwhite,               /* \ */
+       rescan,                 /* ] */
+       joinline,               /* ^ */
+       rescan,                 /* _ */
+       rescan,                 /* ` */
+       rescan,                 /* a */
+       backword,               /* b */
+       capword,                /* c */
+       delfword,               /* d */
+       rescan,                 /* e */
+       forwword                /* f */
+};
+
+static PF metal[] = {
+       lowerword,              /* l */
+       backtoindent,           /* m */
+       rescan,                 /* n */
+       rescan,                 /* o */
+       rescan,                 /* p */
+       fillpara,               /* q */
+       backsearch,             /* r */
+       forwsearch,             /* s */
+       rescan,                 /* t */
+       upperword,              /* u */
+       backpage,               /* v */
+       copyregion,             /* w */
+       extend,                 /* x */
+       rescan,                 /* y */
+       rescan,                 /* z */
+       gotobop,                /* { */
+       piperegion,             /* | */
+       gotoeop                 /* } */
+};
+
+static PF metasqlZ[] = {
+       rescan                  /* Z */
+};
+
+static PF metatilde[] = {
+       notmodified,            /* ~ */
+       delbword                /* DEL */
+};
+
+struct KEYMAPE (1 + IMAPEXT) metasqlmap = {
+       1,
+       1 + IMAPEXT,
+       rescan,
+       {
+               {
+                       'Z', 'Z', metasqlZ, NULL
+               }
+       }
+};
+
+struct KEYMAPE (8 + IMAPEXT) metamap = {
+       8,
+       8 + IMAPEXT,
+       rescan,
+       {
+               {
+                       CCHR('G'), CCHR('G'), metacG, NULL
+               },
+               {
+                       CCHR('V'), CCHR('V'), metacV, NULL
+               },
+               {
+                       ' ', ' ', metasp, NULL
+               },
+               {
+                       '%', '%', metapct, NULL
+               },
+               {
+                       '*', '>', metami, NULL
+               },
+               {
+                       '[', 'f', metasqf, (KEYMAP *) &metasqlmap
+               },
+               {
+                       'l', '}', metal, NULL
+               },
+               {
+                       '~', CCHR('?'), metatilde, NULL
+               }
+       }
+};
+
+static PF fund_at[] = {
+       setmark,                /* ^@ */
+       gotobol,                /* ^A */
+       backchar,               /* ^B */
+       NULL,                   /* ^C */
+       forwdel,                /* ^D */
+       gotoeol,                /* ^E */
+       forwchar,               /* ^F */
+       ctrlg,                  /* ^G */
+};
+
+static PF fund_h[] = {
+       NULL,                   /* ^H */
+};
+
+
+/* ^I is selfinsert */
+static PF fund_CJ[] = {
+       lfindent,               /* ^J */
+       killline,               /* ^K */
+       reposition,             /* ^L */
+       newline,                /* ^M */
+       forwline,               /* ^N */
+       openline,               /* ^O */
+       backline,               /* ^P */
+       quote,                  /* ^Q */
+       backisearch,            /* ^R */
+       forwisearch,            /* ^S */
+       twiddle,                /* ^T */
+       universal_argument,     /* ^U */
+       forwpage,               /* ^V */
+       killregion,             /* ^W */
+       NULL,                   /* ^X */
+       yank,                   /* ^Y */
+       spawncli                /* ^Z */
+};
+
+static PF fund_esc[] = {
+       NULL,                   /* esc */
+       rescan,                 /* ^\ selfinsert is default on fundamental */
+       rescan,                 /* ^] */
+       rescan,                 /* ^^ */
+       undo                    /* ^_ */
+};
+
+static PF fund_del[] = {
+       backdel                 /* DEL */
+};
+
+static PF fund_cb[] = {
+       showmatch               /* )  */
+};
+
+#ifndef        FUND_XMAPS
+#define NFUND_XMAPS    0       /* extra map sections after normal ones */
+#endif
+
+static struct KEYMAPE (6 + NFUND_XMAPS + IMAPEXT) fundmap = {
+       6 + NFUND_XMAPS,
+       6 + NFUND_XMAPS + IMAPEXT,
+       selfinsert,
+       {
+               {
+                       CCHR('@'), CCHR('G'), fund_at, (KEYMAP *) & ccmap
+               },
+               {
+                       CCHR('H'), CCHR('H'), fund_h, (KEYMAP *) & helpmap
+               },
+               {
+                       CCHR('J'), CCHR('Z'), fund_CJ, (KEYMAP *) & cXmap
+               },
+               {
+                       CCHR('['), CCHR('_'), fund_esc, (KEYMAP *) & metamap
+               },
+               {
+                       ')', ')', fund_cb, NULL
+               },
+               {
+                       CCHR('?'), CCHR('?'), fund_del, NULL
+               },
+#ifdef FUND_XMAPS
+               FUND_XMAPS,
+#endif /* FUND_XMAPS */
+       }
+};
+
+static PF fill_sp[] = {
+       fillword                /* ' ' */
+};
+
+static struct KEYMAPE (1 + IMAPEXT) fillmap = {
+       1,
+       1 + IMAPEXT,
+       rescan,
+       {
+               { ' ', ' ', fill_sp, NULL }
+       }
+};
+
+static PF indent_lf[] = {
+       newline,                /* ^J */
+       rescan,                 /* ^K */
+       rescan,                 /* ^L */
+       lfindent                /* ^M */
+};
+
+static struct KEYMAPE (1 + IMAPEXT) indntmap = {
+       1,
+       1 + IMAPEXT,
+       rescan,
+       {
+               {
+                       CCHR('J'), CCHR('M'), indent_lf, NULL
+               }
+       }
+};
+
+#ifdef NOTAB
+static PF notab_tab[] = {
+       space_to_tabstop        /* ^I */
+};
+
+static struct KEYMAPE (1 + IMAPEXT) notabmap = {
+       1,
+       1 + IMAPEXT,
+       rescan,
+       {
+               {
+                       CCHR('I'), CCHR('I'), notab_tab, NULL
+               }
+       }
+};
+#endif /* NOTAB */
+
+static struct KEYMAPE (1 + IMAPEXT) overwmap = {
+       0,
+       1 + IMAPEXT,            /* 1 to avoid 0 sized array */
+       rescan,
+       {
+               /* unused dummy entry for VMS C */
+               {
+                       (KCHAR)0, (KCHAR)0, NULL, NULL
+               }
+       }
+};
+
+
+/*
+ * The basic (root) keyboard map
+ */
+struct maps_s  fundamental_mode = { (KEYMAP *)&fundmap, "fundamental" };
+
+/*
+ * give names to the maps, for use by help etc. If the map is to be bindable,
+ * it must also be listed in the function name table below with the same
+ * name. Maps created dynamically currently don't get added here, thus are
+ * unnamed. Modes are just named keymaps with functions to add/subtract them
+ * from a buffer's list of modes.  If you change a mode name, change it in
+ * modes.c also.
+ */
+
+static struct maps_s map_table[] = {
+       {(KEYMAP *) &fillmap, "fill",},
+       {(KEYMAP *) &indntmap, "indent",},
+#ifdef NOTAB
+       {(KEYMAP *) &notabmap, "notab",},
+#endif /* NOTAB */
+       {(KEYMAP *) &overwmap, "overwrite",},
+       {(KEYMAP *) &metamap, "esc prefix",},
+       {(KEYMAP *) &cXmap, "c-x prefix",},
+       {(KEYMAP *) &cX4map, "c-x 4 prefix",},
+       {(KEYMAP *) &helpmap, "help",},
+       {NULL, NULL}
+};
+
+struct maps_s *maps;
+
+void
+maps_init(void)
+{
+       int      i;
+       struct maps_s   *mp;
+
+       maps = &fundamental_mode;
+       for (i = 0; map_table[i].p_name != NULL; i++) {
+               mp = &map_table[i];
+               mp->p_next = maps;
+               maps = mp;
+       }
+}
+
+/*
+ * Insert a new (named) keymap at the head of the keymap list.
+ */
+int
+maps_add(KEYMAP *map, const char *name)
+{
+       struct maps_s   *mp;
+
+       if ((mp = malloc(sizeof(*mp))) == NULL)
+               return (FALSE);
+
+       mp->p_name = name;
+       mp->p_map = map;
+       mp->p_next = maps;
+       maps = mp;
+
+       return (TRUE);
+}
+
+struct maps_s *
+name_mode(const char *name)
+{
+       struct maps_s   *mp;
+
+       for (mp = maps; mp != NULL; mp = mp->p_next)
+               if (strcmp(mp->p_name, name) == 0)
+                       return (mp);
+       return (NULL);
+}
+
+KEYMAP *
+name_map(const char *name)
+{
+       struct maps_s   *mp;
+
+       return ((mp = name_mode(name)) == NULL ? NULL : mp->p_map);
+}
diff --git a/mg/line.c b/mg/line.c
new file mode 100644 (file)
index 0000000..bafd64f
--- /dev/null
+++ b/mg/line.c
@@ -0,0 +1,635 @@
+/*     $OpenBSD: line.c,v 1.50 2011/01/18 16:28:00 kjell Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ *             Text line handling.
+ *
+ * The functions in this file are a general set of line management
+ * utilities. They are the only routines that touch the text. They
+ * also touch the buffer and window structures to make sure that the
+ * necessary updating gets done.  There are routines in this file that
+ * handle the kill buffer too.  It isn't here for any good reason.
+ *
+ * Note that this code only updates the dot and mark values in the window
+ * list.  Since all the code acts on the current window, the buffer that
+ * we are editing must be displayed, which means that "b_nwnd" is non-zero,
+ * which means that the dot and mark values in the buffer headers are
+ * nonsense.
+ */
+
+#include "def.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * Allocate a new line of size `used'.  lrealloc() can be called if the line
+ * ever needs to grow beyond that.
+ */
+struct line *
+lalloc(int used)
+{
+       struct line *lp;
+
+       if ((lp = malloc(sizeof(*lp))) == NULL)
+               return (NULL);
+       lp->l_text = NULL;
+       lp->l_size = 0;
+       lp->l_used = used;      /* XXX */
+       if (lrealloc(lp, used) == FALSE) {
+               free(lp);
+               return (NULL);
+       }
+       return (lp);
+}
+
+int
+lrealloc(struct line *lp, int newsize)
+{
+       char *tmp;
+
+       if (lp->l_size < newsize) {
+               if ((tmp = realloc(lp->l_text, newsize)) == NULL)
+                       return (FALSE);
+               lp->l_text = tmp;
+               lp->l_size = newsize;
+       }
+       return (TRUE);
+}
+
+/*
+ * Delete line "lp".  Fix all of the links that might point to it (they are
+ * moved to offset 0 of the next line.  Unlink the line from whatever buffer
+ * it might be in, and release the memory.  The buffers are updated too; the
+ * magic conditions described in the above comments don't hold here.
+ */
+void
+lfree(struct line *lp)
+{
+       struct buffer   *bp;
+       struct mgwin    *wp;
+
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+               if (wp->w_linep == lp)
+                       wp->w_linep = lp->l_fp;
+               if (wp->w_dotp == lp) {
+                       wp->w_dotp = lp->l_fp;
+                       wp->w_doto = 0;
+               }
+               if (wp->w_markp == lp) {
+                       wp->w_markp = lp->l_fp;
+                       wp->w_marko = 0;
+               }
+       }
+       for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
+               if (bp->b_nwnd == 0) {
+                       if (bp->b_dotp == lp) {
+                               bp->b_dotp = lp->l_fp;
+                               bp->b_doto = 0;
+                       }
+                       if (bp->b_markp == lp) {
+                               bp->b_markp = lp->l_fp;
+                               bp->b_marko = 0;
+                       }
+               }
+       }
+       lp->l_bp->l_fp = lp->l_fp;
+       lp->l_fp->l_bp = lp->l_bp;
+       if (lp->l_text != NULL)
+               free(lp->l_text);
+       free(lp);
+}
+
+/*
+ * This routine is called when a character changes in place in the current
+ * buffer. It updates all of the required flags in the buffer and window
+ * system. The flag used is passed as an argument; if the buffer is being
+ * displayed in more than 1 window we change EDIT to HARD. Set MODE if the
+ * mode line needs to be updated (the "*" has to be set).
+ */
+void
+lchange(int flag)
+{
+       struct mgwin    *wp;
+
+       /* update mode lines if this is the first change. */
+       if ((curbp->b_flag & BFCHG) == 0) {
+               flag |= WFMODE;
+               curbp->b_flag |= BFCHG;
+       }
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+               if (wp->w_bufp == curbp) {
+                       wp->w_rflag |= flag;
+                       if (wp != curwp)
+                               wp->w_rflag |= WFFULL;
+               }
+       }
+}
+
+/*
+ * Insert "n" bytes from "s" at the current location of dot.
+ * In the easy case all that happens is the text is stored in the line.
+ * In the hard case, the line has to be reallocated.  When the window list
+ * is updated, take special care; I screwed it up once.  You always update
+ * dot in the current window.  You update mark and a dot in another window
+ * if it is greater than the place where you did the insert. Return TRUE
+ * if all is well, and FALSE on errors.
+ */
+int
+linsert_str(const char *s, int n)
+{
+       struct line     *lp1;
+       struct mgwin    *wp;
+       RSIZE    i;
+       int      doto, k;
+
+       if ((k = checkdirty(curbp)) != TRUE)
+               return (k);
+
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read only");
+               return (FALSE);
+       }
+
+       if (!n)
+               return (TRUE);
+
+       lchange(WFFULL);
+
+       /* current line */
+       lp1 = curwp->w_dotp;
+
+       /* special case for the end */
+       if (lp1 == curbp->b_headp) {
+               struct line *lp2, *lp3;
+
+               /* now should only happen in empty buffer */
+               if (curwp->w_doto != 0)
+                       panic("bug: linsert_str");
+               /* allocate a new line */
+               if ((lp2 = lalloc(n)) == NULL)
+                       return (FALSE);
+               /* previous line */
+               lp3 = lp1->l_bp;
+               /* link in */
+               lp3->l_fp = lp2;
+               lp2->l_fp = lp1;
+               lp1->l_bp = lp2;
+               lp2->l_bp = lp3;
+               for (i = 0; i < n; ++i)
+                       lp2->l_text[i] = s[i];
+               for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+                       if (wp->w_linep == lp1)
+                               wp->w_linep = lp2;
+                       if (wp->w_dotp == lp1)
+                               wp->w_dotp = lp2;
+                       if (wp->w_markp == lp1)
+                               wp->w_markp = lp2;
+               }
+               undo_add_insert(lp2, 0, n);
+               curwp->w_doto = n;
+               return (TRUE);
+       }
+       /* save for later */
+       doto = curwp->w_doto;
+
+       if ((lp1->l_used + n) > lp1->l_size) {
+               if (lrealloc(lp1, lp1->l_used + n) == FALSE)
+                       return (FALSE);
+       }
+       lp1->l_used += n;
+       if (lp1->l_used != n)
+               memmove(&lp1->l_text[doto + n], &lp1->l_text[doto],
+                   lp1->l_used - n - doto);
+
+       /* Add the characters */
+       for (i = 0; i < n; ++i)
+               lp1->l_text[doto + i] = s[i];
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+               if (wp->w_dotp == lp1) {
+                       if (wp == curwp || wp->w_doto > doto)
+                               wp->w_doto += n;
+               }
+               if (wp->w_markp == lp1) {
+                       if (wp->w_marko > doto)
+                               wp->w_marko += n;
+               }
+       }
+       undo_add_insert(curwp->w_dotp, doto, n);
+       return (TRUE);
+}
+
+/*
+ * Insert "n" copies of the character "c" at the current location of dot.
+ * In the easy case all that happens is the text is stored in the line.
+ * In the hard case, the line has to be reallocated.  When the window list
+ * is updated, take special care; I screwed it up once.  You always update
+ * dot in the current window.  You update mark and a dot in another window
+ * if it is greater than the place where you did the insert. Return TRUE
+ * if all is well, and FALSE on errors.
+ */
+int
+linsert(int n, int c)
+{
+       struct line     *lp1;
+       struct mgwin    *wp;
+       RSIZE    i;
+       int      doto;
+       int s;
+
+       if (!n)
+               return (TRUE);
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read only");
+               return (FALSE);
+       }
+
+       lchange(WFEDIT);
+
+       /* current line */
+       lp1 = curwp->w_dotp;
+
+       /* special case for the end */
+       if (lp1 == curbp->b_headp) {
+               struct line *lp2, *lp3;
+
+               /* now should only happen in empty buffer */
+               if (curwp->w_doto != 0) {
+                       ewprintf("bug: linsert");
+                       return (FALSE);
+               }
+               /* allocate a new line */
+               if ((lp2 = lalloc(n)) == NULL)
+                       return (FALSE);
+               /* previous line */
+               lp3 = lp1->l_bp;
+               /* link in */
+               lp3->l_fp = lp2;
+               lp2->l_fp = lp1;
+               lp1->l_bp = lp2;
+               lp2->l_bp = lp3;
+               for (i = 0; i < n; ++i)
+                       lp2->l_text[i] = c;
+               for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+                       if (wp->w_linep == lp1)
+                               wp->w_linep = lp2;
+                       if (wp->w_dotp == lp1)
+                               wp->w_dotp = lp2;
+                       if (wp->w_markp == lp1)
+                               wp->w_markp = lp2;
+               }
+               undo_add_insert(lp2, 0, n);
+               curwp->w_doto = n;
+               return (TRUE);
+       }
+       /* save for later */
+       doto = curwp->w_doto;
+
+       if ((lp1->l_used + n) > lp1->l_size) {
+               if (lrealloc(lp1, lp1->l_used + n) == FALSE)
+                       return (FALSE);
+       }
+       lp1->l_used += n;
+       if (lp1->l_used != n)
+               memmove(&lp1->l_text[doto + n], &lp1->l_text[doto],
+                   lp1->l_used - n - doto);
+
+       /* Add the characters */
+       for (i = 0; i < n; ++i)
+               lp1->l_text[doto + i] = c;
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+               if (wp->w_dotp == lp1) {
+                       if (wp == curwp || wp->w_doto > doto)
+                               wp->w_doto += n;
+               }
+               if (wp->w_markp == lp1) {
+                       if (wp->w_marko > doto)
+                               wp->w_marko += n;
+               }
+       }
+       undo_add_insert(curwp->w_dotp, doto, n);
+       return (TRUE);
+}
+
+/*
+ * Do the work of inserting a newline at the given line/offset.
+ * If mark is on the current line, we may have to move the markline
+ * to keep line numbers in sync.
+ * lnewline_at assumes the current buffer is writable. Checking for
+ * this fact should be done by the caller.
+ */
+int
+lnewline_at(struct line *lp1, int doto)
+{
+       struct line     *lp2;
+       int      nlen;
+       struct mgwin    *wp;
+
+       lchange(WFFULL);
+
+       curwp->w_bufp->b_lines++;
+       /* Check if mark is past dot (even on current line) */
+       if (curwp->w_markline > curwp->w_dotline  ||
+          (curwp->w_dotline == curwp->w_markline &&
+           curwp->w_marko >= doto))
+               curwp->w_markline++;
+       curwp->w_dotline++;
+
+       /* If start of line, allocate a new line instead of copying */
+       if (doto == 0) {
+               /* new first part */
+               if ((lp2 = lalloc(0)) == NULL)
+                       return (FALSE);
+               lp2->l_bp = lp1->l_bp;
+               lp1->l_bp->l_fp = lp2;
+               lp2->l_fp = lp1;
+               lp1->l_bp = lp2;
+               for (wp = wheadp; wp != NULL; wp = wp->w_wndp)
+                       if (wp->w_linep == lp1)
+                               wp->w_linep = lp2;
+               undo_add_boundary(FFRAND, 1);
+               undo_add_insert(lp2, 0, 1);
+               undo_add_boundary(FFRAND, 1);
+               return (TRUE);
+       }
+
+       /* length of new part */
+       nlen = llength(lp1) - doto;
+
+       /* new second half line */
+       if ((lp2 = lalloc(nlen)) == NULL)
+               return (FALSE);
+       if (nlen != 0)
+               bcopy(&lp1->l_text[doto], &lp2->l_text[0], nlen);
+       lp1->l_used = doto;
+       lp2->l_bp = lp1;
+       lp2->l_fp = lp1->l_fp;
+       lp1->l_fp = lp2;
+       lp2->l_fp->l_bp = lp2;
+       /* Windows */
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+               if (wp->w_dotp == lp1 && wp->w_doto >= doto) {
+                       wp->w_dotp = lp2;
+                       wp->w_doto -= doto;
+               }
+               if (wp->w_markp == lp1 && wp->w_marko >= doto) {
+                       wp->w_markp = lp2;
+                       wp->w_marko -= doto;
+               }
+       }
+       undo_add_boundary(FFRAND, 1);
+       undo_add_insert(lp1, llength(lp1), 1);
+       undo_add_boundary(FFRAND, 1);
+       return (TRUE);
+}
+
+/*
+ * Insert a newline into the buffer at the current location of dot in the
+ * current window.
+ */
+int
+lnewline(void)
+{
+       int s;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read only");
+               return (FALSE);
+       }
+       return (lnewline_at(curwp->w_dotp, curwp->w_doto));
+}
+
+/*
+ * This function deletes "n" bytes, starting at dot. (actually, n+1, as the
+ * newline is included) It understands how to deal with end of lines, etc.
+ * It returns TRUE if all of the characters were deleted, and FALSE if
+ * they were not (because dot ran into the end of the buffer).
+ * The "kflag" indicates either no insertion, or direction  of insertion
+ * into the kill buffer.
+ */
+int
+ldelete(RSIZE n, int kflag)
+{
+       struct line     *dotp;
+       RSIZE            chunk;
+       struct mgwin    *wp;
+       int              doto;
+       char            *cp1, *cp2;
+       size_t           len;
+       char            *sv = NULL;
+       int              end;
+       int              s;
+       int              rval = FALSE;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read only");
+               goto out;
+       }
+       len = n;
+       if ((sv = calloc(1, len + 1)) == NULL)
+               goto out;
+       end = 0;
+
+       undo_add_delete(curwp->w_dotp, curwp->w_doto, n, (kflag & KREG));
+
+       while (n != 0) {
+               dotp = curwp->w_dotp;
+               doto = curwp->w_doto;
+               /* Hit the end of the buffer */
+               if (dotp == curbp->b_headp)
+                       goto out;
+               /* Size of the chunk */
+               chunk = dotp->l_used - doto;
+
+               if (chunk > n)
+                       chunk = n;
+               /* End of line, merge */
+               if (chunk == 0) {
+                       if (dotp == blastlp(curbp))
+                               goto out;
+                       lchange(WFFULL);
+                       if (ldelnewline() == FALSE)
+                               goto out;
+                       end = strlcat(sv, "\n", len + 1);
+                       --n;
+                       continue;
+               }
+               lchange(WFEDIT);
+               /* Scrunch text */
+               cp1 = &dotp->l_text[doto];
+               memcpy(&sv[end], cp1, chunk);
+               end += chunk;
+               sv[end] = '\0';
+               for (cp2 = cp1 + chunk; cp2 < &dotp->l_text[dotp->l_used];
+                   cp2++)
+                       *cp1++ = *cp2;
+               dotp->l_used -= (int)chunk;
+               for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+                       if (wp->w_dotp == dotp && wp->w_doto >= doto) {
+                               /* NOSTRICT */
+                               wp->w_doto -= chunk;
+                               if (wp->w_doto < doto)
+                                       wp->w_doto = doto;
+                       }
+                       if (wp->w_markp == dotp && wp->w_marko >= doto) {
+                               /* NOSTRICT */
+                               wp->w_marko -= chunk;
+                               if (wp->w_marko < doto)
+                                       wp->w_marko = doto;
+                       }
+               }
+               n -= chunk;
+       }
+       if (kchunk(sv, (RSIZE)len, kflag) != TRUE)
+               goto out;
+       rval = TRUE;
+out:
+       free(sv);
+       return (rval);
+}
+
+/*
+ * Delete a newline and join the current line with the next line. If the next
+ * line is the magic header line always return TRUE; merging the last line
+ * with the header line can be thought of as always being a successful
+ * operation.  Even if nothing is done, this makes the kill buffer work
+ * "right". If the mark is past the dot (actually, markline > dotline),
+ * decrease the markline accordingly to keep line numbers in sync.
+ * Easy cases can be done by shuffling data around.  Hard cases
+ * require that lines be moved about in memory.  Return FALSE on error and
+ * TRUE if all looks ok. We do not update w_dotline here, as deletes are done
+ * after moves.
+ */
+int
+ldelnewline(void)
+{
+       struct line     *lp1, *lp2, *lp3;
+       struct mgwin    *wp;
+       int s;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read only");
+               return (FALSE);
+       }
+
+       lp1 = curwp->w_dotp;
+       lp2 = lp1->l_fp;
+       /* at the end of the buffer */
+       if (lp2 == curbp->b_headp)
+               return (TRUE);
+       /* Keep line counts in sync */
+       curwp->w_bufp->b_lines--;
+       if (curwp->w_markline > curwp->w_dotline)
+               curwp->w_markline--;
+       if (lp2->l_used <= lp1->l_size - lp1->l_used) {
+               bcopy(&lp2->l_text[0], &lp1->l_text[lp1->l_used], lp2->l_used);
+               for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+                       if (wp->w_linep == lp2)
+                               wp->w_linep = lp1;
+                       if (wp->w_dotp == lp2) {
+                               wp->w_dotp = lp1;
+                               wp->w_doto += lp1->l_used;
+                       }
+                       if (wp->w_markp == lp2) {
+                               wp->w_markp = lp1;
+                               wp->w_marko += lp1->l_used;
+                       }
+               }
+               lp1->l_used += lp2->l_used;
+               lp1->l_fp = lp2->l_fp;
+               lp2->l_fp->l_bp = lp1;
+               free(lp2);
+               return (TRUE);
+       }
+       if ((lp3 = lalloc(lp1->l_used + lp2->l_used)) == NULL)
+               return (FALSE);
+       bcopy(&lp1->l_text[0], &lp3->l_text[0], lp1->l_used);
+       bcopy(&lp2->l_text[0], &lp3->l_text[lp1->l_used], lp2->l_used);
+       lp1->l_bp->l_fp = lp3;
+       lp3->l_fp = lp2->l_fp;
+       lp2->l_fp->l_bp = lp3;
+       lp3->l_bp = lp1->l_bp;
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+               if (wp->w_linep == lp1 || wp->w_linep == lp2)
+                       wp->w_linep = lp3;
+               if (wp->w_dotp == lp1)
+                       wp->w_dotp = lp3;
+               else if (wp->w_dotp == lp2) {
+                       wp->w_dotp = lp3;
+                       wp->w_doto += lp1->l_used;
+               }
+               if (wp->w_markp == lp1)
+                       wp->w_markp = lp3;
+               else if (wp->w_markp == lp2) {
+                       wp->w_markp = lp3;
+                       wp->w_marko += lp1->l_used;
+               }
+       }
+       free(lp1);
+       free(lp2);
+       return (TRUE);
+}
+
+/*
+ * Replace plen characters before dot with argument string.  Control-J
+ * characters in st are interpreted as newlines.  There is a casehack
+ * disable flag (normally it likes to match case of replacement to what
+ * was there).
+ */
+int
+lreplace(RSIZE plen, char *st)
+{
+       RSIZE   rlen;   /* replacement length            */
+       int s;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read only");
+               return (FALSE);
+       }
+       undo_boundary_enable(FFRAND, 0);
+
+       (void)backchar(FFARG | FFRAND, (int)plen);
+       (void)ldelete(plen, KNONE);
+
+       rlen = strlen(st);
+       region_put_data(st, rlen);
+       lchange(WFFULL);
+
+       undo_boundary_enable(FFRAND, 1);
+       return (TRUE);
+}
+
+/*
+ * Allocate and return the supplied line as a C string
+ */
+char *
+linetostr(const struct line *ln)
+{
+       int      len;
+       char    *line;
+
+       len = llength(ln);
+       if (len == INT_MAX)  /* (len + 1) overflow */
+               return (NULL);
+
+       if ((line = malloc(len + 1)) == NULL)
+               return (NULL);
+
+       (void)memcpy(line, ltext(ln), len);
+       line[len] = '\0';
+
+       return (line);
+}
diff --git a/mg/macro.c b/mg/macro.c
new file mode 100644 (file)
index 0000000..1910478
--- /dev/null
@@ -0,0 +1,106 @@
+/*     $OpenBSD: macro.c,v 1.14 2012/04/12 04:47:59 lum Exp $  */
+
+/* This file is in the public domain. */
+
+/*
+ *     Keyboard macros.
+ */
+
+#include "def.h"
+#include "key.h"
+#include "macro.h"
+
+int inmacro = FALSE;   /* Macro playback in progess */
+int macrodef = FALSE;  /* Macro recording in progress */
+int macrocount = 0;
+
+struct line *maclhead = NULL;
+struct line *maclcur;
+
+union macrodef macro[MAXMACRO];
+
+/* ARGSUSED */
+int
+definemacro(int f, int n)
+{
+       struct line     *lp1, *lp2;
+
+       macrocount = 0;
+
+       if (macrodef) {
+               ewprintf("already defining macro");
+               return (macrodef = FALSE);
+       }
+
+       /* free lines allocated for string arguments */
+       if (maclhead != NULL) {
+               for (lp1 = maclhead->l_fp; lp1 != maclhead; lp1 = lp2) {
+                       lp2 = lp1->l_fp;
+                       free(lp1);
+               }
+               free(lp1);
+       }
+
+       if ((maclhead = lp1 = lalloc(0)) == NULL)
+               return (FALSE);
+
+       ewprintf("Defining Keyboard Macro...");
+       maclcur = lp1->l_fp = lp1->l_bp = lp1;
+       return (macrodef = TRUE);
+}
+
+/* ARGSUSED */
+int
+finishmacro(int f, int n)
+{
+       if (macrodef == TRUE) {
+               macrodef = FALSE;
+               ewprintf("End Keyboard Macro Definition");
+               return (TRUE);
+       }
+       return (FALSE);
+}
+
+/* ARGSUSED */
+int
+executemacro(int f, int n)
+{
+       int      i, j, flag, num;
+       PF       funct;
+
+       if (macrodef ||
+           (macrocount >= MAXMACRO && macro[MAXMACRO - 1].m_funct
+           != finishmacro)) {
+               ewprintf("Macro too long. Aborting.");
+               return (FALSE);
+       }
+
+       if (macrocount == 0)
+               return (TRUE);
+
+       inmacro = TRUE;
+
+       for (i = n; i > 0; i--) {
+               maclcur = maclhead->l_fp;
+               flag = 0;
+               num = 1;
+               for (j = 0; j < macrocount - 1; j++) {
+                       funct = macro[j].m_funct;
+                       if (funct == universal_argument) {
+                               flag = FFARG;
+                               num = macro[++j].m_count;
+                               continue;
+                       }
+                       if ((*funct)(flag, num) != TRUE) {
+                               inmacro = FALSE;
+                               return (FALSE);
+                       }
+                       lastflag = thisflag;
+                       thisflag = 0;
+                       flag = 0;
+                       num = 1;
+               }
+       }
+       inmacro = FALSE;
+       return (TRUE);
+}
diff --git a/mg/macro.h b/mg/macro.h
new file mode 100644 (file)
index 0000000..52cc83d
--- /dev/null
@@ -0,0 +1,21 @@
+/*     $OpenBSD: macro.h,v 1.7 2005/11/18 20:56:53 deraadt Exp $       */
+
+/* This file is in the public domain. */
+
+/* definitions for keyboard macros */
+
+#define MAXMACRO 256           /* maximum functs in a macro */
+
+extern int inmacro;
+extern int macrodef;
+extern int macrocount;
+
+union macrodef {
+       PF      m_funct;
+       int     m_count;        /* for count-prefix      */
+};
+
+extern union macrodef macro[MAXMACRO];
+
+extern struct line     *maclhead;
+extern struct line     *maclcur;
diff --git a/mg/main.c b/mg/main.c
new file mode 100644 (file)
index 0000000..c93eb9f
--- /dev/null
+++ b/mg/main.c
@@ -0,0 +1,245 @@
+/*     $OpenBSD: main.c,v 1.67 2012/05/29 06:08:48 lum Exp $   */
+
+/* This file is in the public domain. */
+
+/*
+ *     Mainline.
+ */
+
+#include "def.h"
+#include "kbd.h"
+#include "funmap.h"
+#include "macro.h"
+
+#include <err.h>
+
+int             thisflag;                      /* flags, this command  */
+int             lastflag;                      /* flags, last command  */
+int             curgoal;                       /* goal column          */
+int             startrow;                      /* row to start         */
+struct buffer  *curbp;                         /* current buffer       */
+struct buffer  *bheadp;                        /* BUFFER list head     */
+struct mgwin   *curwp;                         /* current window       */
+struct mgwin   *wheadp;                        /* MGWIN listhead       */
+char            pat[NPAT];                     /* pattern              */
+
+static void     edinit(struct buffer *);
+static __dead void usage(void);
+
+extern char    *__progname;
+extern void     closetags(void);
+
+static __dead void
+usage()
+{
+       fprintf(stderr, "usage: %s [-n] [-f mode] [+number] [file ...]\n",
+           __progname);
+       exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+       char            *cp, *init_fcn_name = NULL;
+       PF               init_fcn = NULL;
+       int              o, i, nfiles;
+       int              nobackups = 0;
+       struct buffer   *bp = NULL;
+
+       while ((o = getopt(argc, argv, "nf:")) != -1)
+               switch (o) {
+               case 'n':
+                       nobackups = 1;
+                       break;
+               case 'f':
+                       if (init_fcn_name != NULL)
+                               errx(1, "cannot specify more than one "
+                                   "initial function");
+                       init_fcn_name = optarg;
+                       break;
+               default:
+                       usage();
+               }
+       argc -= optind;
+       argv += optind;
+
+       maps_init();            /* Keymaps and modes.           */
+       funmap_init();          /* Functions.                   */
+
+       /*
+        * This is where we initialize standalone extensions that should
+        * be loaded dynamically sometime in the future.
+        */
+       {
+               extern void grep_init(void);
+               extern void theo_init(void);
+               extern void cmode_init(void);
+               extern void dired_init(void);
+
+               dired_init();
+               grep_init();
+               theo_init();
+               cmode_init();
+       }
+
+       if (init_fcn_name &&
+           (init_fcn = name_function(init_fcn_name)) == NULL)
+               errx(1, "Unknown function `%s'", init_fcn_name);
+
+       vtinit();               /* Virtual terminal.            */
+       dirinit();              /* Get current directory.       */
+       edinit(bp);             /* Buffers, windows.            */
+       ttykeymapinit();        /* Symbols, bindings.           */
+
+       /*
+        * doing update() before reading files causes the error messages from
+        * the file I/O show up on the screen.  (and also an extra display of
+        * the mode line if there are files specified on the command line.)
+        */
+       update();
+
+       /* user startup file. */
+       if ((cp = startupfile(NULL)) != NULL)
+               (void)load(cp);
+
+       /* 
+        * Now ensure any default buffer modes from the startup file are
+        * given to any files opened when parsing the startup file.
+        * Note *scratch* will also be updated.
+        */
+       for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
+               bp->b_flag = defb_flag;
+               for (i = 0; i <= defb_nmodes; i++) {
+                       bp->b_modes[i] = defb_modes[i];
+               }
+       }
+
+       /* Force FFOTHARG=1 so that this mode is enabled, not simply toggled */
+       if (init_fcn)
+               init_fcn(FFOTHARG, 1);
+
+       if (nobackups)
+               makebkfile(FFARG, 0);
+
+       for (nfiles = 0, i = 0; i < argc; i++) {
+               if (argv[i][0] == '+' && strlen(argv[i]) >= 2) {
+                       long long lval;
+                       const char *errstr;
+
+                       lval = strtonum(&argv[i][1], INT_MIN, INT_MAX, &errstr);
+                       if (argv[i][1] == '\0' || errstr != NULL)
+                               goto notnum;
+                       startrow = lval;
+               } else {
+notnum:
+                       cp = adjustname(argv[i], FALSE);
+                       if (cp != NULL) {
+                               if (nfiles == 1)
+                                       splitwind(0, 1);
+
+                               if ((curbp = findbuffer(cp)) == NULL) {
+                                       vttidy();
+                                       errx(1, "Can't find current buffer!");
+                               }
+                               (void)showbuffer(curbp, curwp, 0);
+                               if (readin(cp) != TRUE)
+                                       killbuffer(curbp);
+                               else {
+                                       /* Ensure enabled, not just toggled */
+                                       if (init_fcn_name)
+                                               init_fcn(FFOTHARG, 1);
+                                       nfiles++;
+                               }
+                       }
+               }
+       }
+
+       if (nfiles > 2)
+               listbuffers(0, 1);
+
+       /* fake last flags */
+       thisflag = 0;
+       for (;;) {
+               if (epresf == KCLEAR)
+                       eerase();
+               if (epresf == TRUE)
+                       epresf = KCLEAR;
+               if (winch_flag) {
+                       do_redraw(0, 0, TRUE);
+                       winch_flag = 0;
+               }
+               update();
+               lastflag = thisflag;
+               thisflag = 0;
+
+               switch (doin()) {
+               case TRUE:
+                       break;
+               case ABORT:
+                       ewprintf("Quit");
+                       /* FALLTHRU */
+               case FALSE:
+               default:
+                       ttbeep();
+                       macrodef = FALSE;
+               }
+       }
+}
+
+/*
+ * Initialize default buffer and window. Default buffer is called *scratch*.
+ */
+static void
+edinit(struct buffer *bp)
+{
+       struct mgwin    *wp;
+
+       bheadp = NULL;
+       bp = bfind("*scratch*", TRUE);          /* Text buffer.          */
+       if (bp == NULL)
+               panic("edinit");
+
+       wp = new_window(bp);
+       if (wp == NULL)
+               panic("edinit: Out of memory");
+
+       curbp = bp;                             /* Current buffer.       */
+       wheadp = wp;
+       curwp = wp;
+       wp->w_wndp = NULL;                      /* Initialize window.    */
+       wp->w_linep = wp->w_dotp = bp->b_headp;
+       wp->w_ntrows = nrow - 2;                /* 2 = mode, echo.       */
+       wp->w_rflag = WFMODE | WFFULL;          /* Full.                 */
+}
+
+/*
+ * Quit command.  If an argument, always quit.  Otherwise confirm if a buffer
+ * has been changed and not written out.  Normally bound to "C-X C-C".
+ */
+/* ARGSUSED */
+int
+quit(int f, int n)
+{
+       int      s;
+
+       if ((s = anycb(FALSE)) == ABORT)
+               return (ABORT);
+       if (s == FALSE
+           || eyesno("Modified buffers exist; really exit") == TRUE) {
+               vttidy();
+               closetags();
+               exit(GOOD);
+       }
+       return (TRUE);
+}
+
+/*
+ * User abort.  Should be called by any input routine that sees a C-g to abort
+ * whatever C-g is aborting these days. Currently does nothing.
+ */
+/* ARGSUSED */
+int
+ctrlg(int f, int n)
+{
+       return (ABORT);
+}
diff --git a/mg/match.c b/mg/match.c
new file mode 100644 (file)
index 0000000..4c5d0c3
--- /dev/null
@@ -0,0 +1,185 @@
+/*     $OpenBSD: match.c,v 1.16 2009/06/04 02:23:37 kjell Exp $        */
+
+/* This file is in the public domain. */
+
+/*
+ *     Limited parenthesis matching routines
+ *
+ * The hacks in this file implement automatic matching * of (), [], {}, and
+ * other characters.  It would be better to have a full-blown syntax table,
+ * but there's enough overhead in the editor as it is.
+ */
+
+#include "def.h"
+#include "key.h"
+
+static int     balance(void);
+static void    displaymatch(struct line *, int);
+
+/*
+ * Balance table. When balance() encounters a character that is to be
+ * matched, it first searches this table for a balancing left-side character.
+ * If the character is not in the table, the character is balanced by itself.
+ */
+static struct balance {
+       char    left, right;
+} bal[] = {
+       { '(', ')' },
+       { '[', ']' },
+       { '{', '}' },
+       { '<', '>' },
+       { '\0', '\0' }
+};
+
+/*
+ * Hack to show matching paren.  Self-insert character, then show matching
+ * character, if any.  Bound to "blink-and-insert".
+ */
+int
+showmatch(int f, int n)
+{
+       int     i, s;
+
+       for (i = 0; i < n; i++) {
+               if ((s = selfinsert(FFRAND, 1)) != TRUE)
+                       return (s);
+               /* unbalanced -- warn user */
+               if (balance() != TRUE)
+                       ttbeep();
+       }
+       return (TRUE);
+}
+
+/*
+ * Search for and display a matching character.
+ *
+ * This routine does the real work of searching backward
+ * for a balancing character.  If such a balancing character
+ * is found, it uses displaymatch() to display the match.
+ */
+static int
+balance(void)
+{
+       struct line     *clp;
+       int      cbo;
+       int      c, i, depth;
+       int      rbal, lbal;
+
+       rbal = key.k_chars[key.k_count - 1];
+
+       /* See if there is a matching character -- default to the same */
+       lbal = rbal;
+       for (i = 0; bal[i].right != '\0'; i++)
+               if (bal[i].right == rbal) {
+                       lbal = bal[i].left;
+                       break;
+               }
+
+       /*
+        * Move behind the inserted character.  We are always guaranteed
+        * that there is at least one character on the line, since one was
+        * just self-inserted by blinkparen.
+        */
+       clp = curwp->w_dotp;
+       cbo = curwp->w_doto - 1;
+
+       /* init nesting depth */
+       depth = 0;
+
+       for (;;) {
+               if (cbo == 0) {
+                       clp = lback(clp);       /* beginning of line    */
+                       if (clp == curbp->b_headp)
+                               return (FALSE);
+                       cbo = llength(clp) + 1;
+               }
+               if (--cbo == llength(clp))
+                       c = '\n';               /* end of line          */
+               else
+                       c = lgetc(clp, cbo);    /* somewhere in middle  */
+
+               /*
+                * Check for a matching character.  If still in a nested
+                * level, pop out of it and continue search.  This check
+                * is done before the nesting check so single-character
+                * matches will work too.
+                */
+               if (c == lbal) {
+                       if (depth == 0) {
+                               displaymatch(clp, cbo);
+                               return (TRUE);
+                       } else
+                               depth--;
+               }
+               /* Check for another level of nesting.   */
+               if (c == rbal)
+                       depth++;
+       }
+       /* NOTREACHED */
+}
+
+/*
+ * Display matching character.  Matching characters that are not in the
+ * current window are displayed in the echo line. If in the current window,
+ * move dot to the matching character, sit there a while, then move back.
+ */
+static void
+displaymatch(struct line *clp, int cbo)
+{
+       struct line     *tlp;
+       int      tbo;
+       int      cp;
+       int      bufo;
+       int      c;
+       int      inwindow;
+       char     buf[NLINE];
+
+       /*
+        * Figure out if matching char is in current window by
+        * searching from the top of the window to dot.
+        */
+       inwindow = FALSE;
+       for (tlp = curwp->w_linep; tlp != lforw(curwp->w_dotp);
+           tlp = lforw(tlp))
+               if (tlp == clp)
+                       inwindow = TRUE;
+
+       if (inwindow == TRUE) {
+               tlp = curwp->w_dotp;    /* save current position */
+               tbo = curwp->w_doto;
+
+               curwp->w_dotp = clp;    /* move to new position */
+               curwp->w_doto = cbo;
+               curwp->w_rflag |= WFMOVE;
+
+               update();               /* show match */
+               ttwait(1000);           /* wait for key or 1 second */
+
+               curwp->w_dotp = tlp;    /* return to old position */
+               curwp->w_doto = tbo;
+               curwp->w_rflag |= WFMOVE;
+               update();
+       } else {
+               /* match is not in this window, so display line in echo area */
+               bufo = 0;
+               for (cp = 0; cp < llength(clp); cp++) {
+                       c = lgetc(clp, cp);
+                       if (c != '\t'
+#ifdef NOTAB
+                           || (curbp->b_flag & BFNOTAB)
+#endif
+                               )
+                               if (ISCTRL(c)) {
+                                       buf[bufo++] = '^';
+                                       buf[bufo++] = CCHR(c);
+                               } else
+                                       buf[bufo++] = c;
+                       else
+                               do {
+                                       buf[bufo++] = ' ';
+                               } while (bufo & 7);
+               }
+               buf[bufo++] = '\0';
+               ewprintf("Matches %s", buf);
+       }
+}
diff --git a/mg/mg.1 b/mg/mg.1
new file mode 100644 (file)
index 0000000..134af55
--- /dev/null
+++ b/mg/mg.1
@@ -0,0 +1,956 @@
+.\"    $OpenBSD: mg.1,v 1.68 2012/07/11 19:56:13 sobrado Exp $
+.\" This file is in the public domain.
+.\"
+.Dd $Mdocdate: July 11 2012 $
+.Dt MG 1
+.Os
+.Sh NAME
+.Nm mg
+.Nd emacs-like text editor
+.Sh SYNOPSIS
+.Nm mg
+.Op Fl n
+.Op Fl f Ar mode
+.Op + Ns Ar number
+.Op Ar
+.Sh DESCRIPTION
+.Nm
+is intended to be a small, fast, and portable editor for
+people who can't (or don't want to) run emacs for one
+reason or another, or are not familiar with the
+.Xr vi 1
+editor.
+It is compatible with emacs because there shouldn't
+be any reason to learn more editor types than emacs or
+.Xr vi 1 .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It + Ns Ar number
+Go to the line specified by number (do not insert
+a space between the
+.Sq +
+sign and the number).
+If a negative number is specified, the line number counts
+backwards from the end of the file i.e. +-1 will be the last
+line of the file, +-2 will be second last, and so on.
+.It Fl f Ar mode
+Run the mode command for all buffers created from
+arguments on the command line, including the
+scratch buffer and all files.
+.It Fl n
+Turn off backup file generation.
+.El
+.Sh WINDOWS AND BUFFERS
+When a file is loaded into
+.Nm ,
+it is stored in a
+.Em buffer .
+This buffer may be displayed on the screen in more than one window.
+At present, windows may only be split horizontally, so each window is
+delineated by a modeline at the bottom.
+If changes are made to a buffer, it will be reflected in all open windows.
+.Pp
+If a buffer name begins and ends with an asterisk, the buffer is considered
+throwaway; i.e. the user will not be prompted to save changes when
+the buffer is killed.
+.Sh POINT AND MARK
+The current cursor location in
+.Nm
+is called the
+.Em point
+(or
+.Em dot ) .
+It is possible to define a window-specific region of text by setting a second
+location, called the
+.Em mark .
+The
+.Em region
+is the text between point and mark inclusive.
+Deleting the character at the mark position leaves
+the mark at the point of deletion.
+.Pp
+Note: The point and mark are window-specific in
+.Nm ,
+not buffer-specific, as in other emacs flavours.
+.Sh BACKUP FILES
+Backup files have a
+.Sq ~
+character appended to the file name and
+are created in the current working directory by default.
+Whether to create backup files or not can be toggled with the
+make-backup-file command.
+The backup file location can either be in the current
+working directory, or all backups can be moved to a
+.Pa ~/.mg.d
+directory where files retain their path name to retain uniqueness.
+Use the backup-to-home-directory to alternate between these two locations.
+Further, if any application creates backup files in the
+.Ev TMPDIR ,
+these can be left with the leave-tmpdir-backups command.
+.Sh TAGS
+.Nm
+supports tag files created by
+.Xr ctags 1 ,
+allowing the user to quickly locate various object definitions.
+Note though that emacs uses etags, not ctags.
+.Sh CSCOPE
+.Nm
+supports navigating source code using cscope.
+However,
+.Nm
+requires cscope and cscope-indexer executables to be present in
+.Ev PATH
+for it to work.
+.Sh DEFAULT KEY BINDINGS
+Normal editing commands are very similar to GNU Emacs.
+In the following examples, C-x means Control-x, and M-x means Meta-x,
+where the Meta key may be either a special key on the keyboard
+or the ALT key; otherwise ESC followed by the key X works as well.
+.Pp
+.Bl -tag -width xxxxxxxxxxxx -offset indent -compact
+.It C-SPC
+set-mark-command
+.It C-a
+beginning-of-line
+.It C-b
+backward-char
+.It C-c s c
+cscope-find-functions-calling-this-function
+.It C-c s d
+cscope-find-global-definition
+.It C-c s e
+cscope-find-egrep-pattern
+.It C-c s f
+cscope-find-this-file
+.It C-c s i
+cscope-find-files-including-file
+.It C-c s n
+cscope-next-symbol
+.It C-c s p
+cscope-prev-symbol
+.It C-c s s
+cscope-find-this-symbol
+.It C-c s t
+cscope-find-this-text-string
+.It C-d
+delete-char
+.It C-e
+end-of-line
+.It C-f
+forward-char
+.It C-g
+keyboard-quit
+.It C-h C-h
+help-help
+.It C-h a
+apropos
+.It C-h b
+describe-bindings
+.It C-h c
+describe-key-briefly
+.It C-j
+newline-and-indent
+.It C-k
+kill-line
+.It C-l
+recenter
+.It RET
+newline
+.It C-n
+next-line
+.It C-o
+open-line
+.It C-p
+previous-line
+.It C-q
+quoted-insert
+.It C-r
+isearch-backward
+.It C-s
+isearch-forward
+.It C-t
+transpose-chars
+.It C-u
+universal-argument
+.It C-v
+scroll-up
+.It C-w
+kill-region
+.It C-x C-b
+list-buffers
+.It C-x C-c
+save-buffers-kill-emacs
+.It C-x C-f
+find-file
+.It C-x C-g
+keyboard-quit
+.It C-x C-l
+downcase-region
+.It C-x C-o
+delete-blank-lines
+.It C-x C-q
+toggle-read-only
+.It C-x C-r
+find-file-read-only
+.It C-x C-s
+save-buffer
+.It C-x C-u
+upcase-region
+.It C-x C-v
+find-alternate-file
+.It C-x C-w
+write-file
+.It C-x C-x
+exchange-point-and-mark
+.It C-x (
+start-kbd-macro
+.It C-x \&)
+end-kbd-macro
+.It C-x 0
+delete-window
+.It C-x 1
+delete-other-windows
+.It C-x 2
+split-window-vertically
+.It C-x 4 C-f
+find-file-other-window
+.It C-x 4 C-g
+keyboard-quit
+.It C-x 4 b
+switch-to-buffer-other-window
+.It C-x 4 f
+find-file-other-window
+.It C-x =
+what-cursor-position
+.It C-x ^
+enlarge-window
+.It C-x `
+next-error
+.It C-x b
+switch-to-buffer
+.It C-x d
+dired
+.It C-x e
+call-last-kbd-macro
+.It C-x f
+set-fill-column
+.It C-x g
+goto-line
+.It C-x h
+mark-whole-buffer
+.It C-x i
+insert-file
+.It C-x k
+kill-buffer
+.It C-x n
+other-window
+.It C-x o
+other-window
+.It C-x p
+previous-window
+.It C-x s
+save-some-buffers
+.It C-x u
+undo
+.It C-y
+yank
+.It C-z
+suspend-emacs
+.It M-C-v
+scroll-other-window
+.It M-SPC
+just-one-space
+.It M-.
+find-tag
+.It M-*
+pop-tag-mark
+.It M-%
+query-replace
+.It M-<
+beginning-of-buffer
+.It M->
+end-of-buffer
+.It M-\e
+delete-horizontal-space
+.It M-^
+join-line
+.It M-b
+backward-word
+.It M-c
+capitalize-word
+.It M-d
+kill-word
+.It M-f
+forward-word
+.It M-l
+downcase-word
+.It M-m
+back-to-indentation
+.It M-q
+fill-paragraph
+.It M-r
+search-backward
+.It M-s
+search-forward
+.It M-u
+upcase-word
+.It M-v
+scroll-down
+.It M-w
+copy-region-as-kill
+.It M-x
+execute-extended-command
+.It M-{
+backward-paragraph
+.It M-|
+shell-command-on-region
+.It M-}
+forward-paragraph
+.It M-~
+not-modified
+.It M-DEL
+backward-kill-word
+.It C-_
+undo
+.It )
+blink-and-insert
+.It DEL
+delete-backward-char
+.El
+.Pp
+For a complete description of
+.Nm
+commands, see
+.Sx MG COMMANDS .
+To see the active keybindings at any time, type
+.Dq M-x describe-bindings .
+.Sh MG COMMANDS
+Commands are invoked by
+.Dq M-x ,
+or by binding to a key.
+Many commands take an optional numerical parameter,
+.Va n .
+This parameter is set either by
+M-<n> (where
+.Va n
+is the numerical argument) before the command, or by
+one or more invocations of the universal argument, usually bound to C-u.
+When invoked in this manner, the value of the numeric parameter to
+be passed is displayed in the minibuffer before the M-x.
+One common use of the parameter is in mode toggles (e.g.\&
+make-backup-files).
+If no parameter is supplied, the mode is toggled to its
+alternate state.
+If a positive parameter is supplied, the mode is forced to on.
+Otherwise, it is forced to off.
+.\"
+.Bl -tag -width xxxxx
+.It apropos
+Help Apropos.
+Prompt the user for a string, open the *help* buffer,
+and list all
+.Nm
+commands that contain that string.
+.It auto-execute
+Register an auto-execute hook; that is, specify a filename pattern
+(conforming to the shell's filename globbing rules) and an associated
+function to execute when a file matching the specified pattern
+is read into a buffer.
+.It auto-fill-mode
+Toggle auto-fill mode (sometimes called mail-mode),
+where text inserted past the fill column is automatically wrapped
+to a new line.
+.It auto-indent-mode
+Toggle indent mode, where indentation is preserved after a newline.
+.It back-to-indentation
+Move the dot to the first non-whitespace character on the current line.
+.It backup-to-home-directory
+Save backup copies to a
+.Pa ~/.mg.d
+directory instead of working directory.
+Requires make-backup-files to be on.
+.It backward-char
+Move cursor backwards one character.
+.It backward-kill-word
+Kill text backwards by
+.Va n
+words.
+.It backward-paragraph
+Move cursor backwards
+.Va n
+paragraphs.
+Paragraphs are delimited by <NL><NL> or <NL><TAB> or <NL><SPACE>.
+.It backward-word
+Move cursor backwards by the specified number of words.
+.It beginning-of-buffer
+Move cursor to the top of the buffer.
+.It beginning-of-line
+Move cursor to the beginning of the line.
+.It blink-and-insert
+Self-insert a character, then search backwards and blink its
+matching delimiter.
+For delimiters other than
+parenthesis, brackets, and braces, the character itself
+is used as its own match.
+.It bsmap-mode
+Toggle bsmap mode, where DEL and C-h are swapped.
+.It c-mode
+Toggle a KNF-compliant mode for editing C program files.
+.It call-last-kbd-macro
+Invoke the keyboard macro.
+.It capitalize-word
+Capitalize
+.Va n
+words; i.e. convert the first character of the word to
+upper case, and subsequent letters to lower case.
+.It cd
+Change the global working directory.
+See also global-wd-mode.
+.It copy-region-as-kill
+Copy all of the characters in the region to the kill buffer,
+clearing the mark afterwards.
+This is a bit like a kill-region followed by a yank.
+.It count-matches
+Count the number of lines matching the supplied regular expression.
+.It count-non-matches
+Count the number of lines not matching the supplied regular expression.
+.It cscope-find-this-symbol
+List the matches for the given symbol.
+.It cscope-find-global-definition
+List global definitions for the given literal.
+.It cscope-find-called-functions
+List functions called from the given function.
+.It cscope-find-functions-calling-this-function
+List functions calling the given function.
+.It cscope-find-this-text-string
+List locations matching the given text string.
+.It cscope-find-egrep-pattern
+List locations matching the given extended regular expression pattern.
+.It cscope-find-this-file
+List filenames matching the given filename.
+.It cscope-find-files-including-file
+List files that #include the given filename.
+.It cscope-next-symbol
+Navigate to the next match.
+.It cscope-prev-symbol
+Navigate to the previous match.
+.It cscope-next-file
+Navigate to the next file.
+.It cscope-prev-file
+Navigate to the previous file.
+.It cscope-create-list-of-files-to-index
+Create cscope's List and Index in the given directory.
+.It define-key
+Prompts the user for a named keymap (mode),
+a key, and an
+.Nm
+command, then creates a keybinding in the appropriate
+map.
+.It delete-backward-char
+Delete backwards
+.Va n
+characters.
+Like delete-char, this actually does a kill if presented
+with an argument.
+.It delete-blank-lines
+Delete blank lines around dot.
+If dot is sitting on a blank line, this command
+deletes all the blank lines above and below the current line.
+Otherwise, it deletes all of the blank lines after the current line.
+.It delete-char
+Delete
+.Va n
+characters forward.
+If any argument is present, it kills rather than deletes,
+saving the result in the kill buffer.
+.It delete-horizontal-space
+Delete any whitespace around the dot.
+.It delete-leading-space
+Delete leading whitespace on the current line.
+.It delete-trailing-space
+Delete trailing whitespace on the current line.
+.It delete-matching-lines
+Delete all lines after dot that contain a string matching
+the supplied regular expression.
+.It delete-non-matching-lines
+Delete all lines after dot that contain a string matching
+the supplied regular expression.
+.It delete-other-windows
+Make the current window the only window visible on the screen.
+.It delete-window
+Delete current window.
+.It describe-bindings
+List all global and local keybindings, putting the result in
+the *help* buffer.
+.It describe-key-briefly
+Read a key from the keyboard, and look it up in the keymap.
+Display the name of the function currently bound to the key.
+.It digit-argument
+Process a numerical argument for keyboard-invoked functions.
+.It downcase-region
+Set all characters in the region to lower case.
+.It downcase-word
+Set characters to lower case, starting at the dot, and ending
+.Va n
+words away.
+.It emacs-version
+Return an
+.Nm
+version string.
+.It end-kbd-macro
+Stop defining a keyboard macro.
+.It end-of-buffer
+Move cursor to the end of the buffer.
+.It end-of-line
+Move cursor to the end of the line.
+.It enlarge-window
+Enlarge the current window by shrinking either the window above
+or below it.
+.It eval-current-buffer
+Evaluate the current buffer as a series of
+.Nm
+commands.
+Useful for testing
+.Nm
+startup files.
+.It eval-expression
+Get one line from the user, and run it.
+Useful for testing expressions in
+.Nm
+startup files.
+.It exchange-point-and-mark
+Swap the values of "dot" and "mark" in the current window.
+Return an error if no mark is set.
+.It execute-extended-command
+Invoke an extended command; i.e. M-x.
+Call the message line routine to read in the command name and apply
+autocompletion to it.
+When it comes back, look the name up in the symbol table and run the
+command if it is found, passing arguments as necessary.
+Print an error if there is anything wrong.
+.It fill-paragraph
+Justify a paragraph, wrapping text at the current fill column.
+.It find-file
+Select a file for editing.
+First check if the file can be found
+in another buffer; if it is there, just switch to that buffer.
+If the file cannot be found, create a new buffer, read in the
+file from disk, and switch to the new buffer.
+.It find-file-read-only
+Same as find-file, except the new buffer is set to read-only.
+.It find-alternate-file
+Replace the current file with an alternate one.
+Semantics for finding the replacement file are the same as
+find-file, except the current buffer is killed before the switch.
+If the kill fails, or is aborted, revert to the original file.
+.It find-file-other-window
+Opens the specified file in a second buffer.
+Splits the current window if necessary.
+.It find-tag
+Jump to definition of tag at dot.
+.It forward-char
+Move cursor forwards (or backwards, if
+.Va n
+is negative)
+.Va n
+characters.
+Returns an error if the end of buffer is reached.
+.It forward-paragraph
+Move forward
+.Va n
+paragraphs.
+Paragraphs are delimited by <NL><NL> or <NL><TAB> or <NL><SPACE>.
+.It forward-word
+Move the cursor forward by the specified number of words.
+.It global-set-key
+Bind a key in the global (fundamental) key map.
+.It global-unset-key
+Unbind a key from the global (fundamental) key map; i.e. set it to 'rescan'.
+.It global-wd-mode
+Toggle global working-directory mode.
+When enabled,
+.Nm
+defaults to opening files (and executing commands like compile and grep)
+relative to the global working directory.
+When disabled, a working directory is set for each buffer.
+.It goto-line
+Go to a specific line.
+If an argument is present, then
+it is the line number, else prompt for a line number to use.
+.It help-help
+Prompts for one of (a)propos, (b)indings, des(c)ribe key briefly.
+.It insert
+Insert a string, mainly for use from macros.
+.It insert-buffer
+Insert the contents of another buffer at dot.
+.It insert-file
+Insert a file into the current buffer at dot.
+.It insert-with-wrap
+Insert the bound character with word wrap.
+Check to see if we're past the fill column, and if so,
+justify this line.
+.It isearch-backward
+Use incremental searching, initially in the reverse direction.
+isearch ignores any explicit arguments.
+If invoked during macro definition or evaluation, the non-incremental
+search-backward is invoked instead.
+.It isearch-forward
+Use incremental searching, initially in the forward direction.
+isearch ignores any explicit arguments.
+If invoked during macro definition or evaluation, the non-incremental
+search-forward is invoked instead.
+.It join-line
+Join the current line to the previous.
+If called with an argument,
+join the next line to the current one.
+.It just-one-space
+Delete any whitespace around dot, then insert a space.
+.It keyboard-quit
+Abort the current action.
+.It kill-buffer
+Dispose of a buffer, by name.
+If the buffer name does not start and end with an asterisk,
+prompt the user if the buffer
+has been changed.
+.It kill-line
+Kill line.
+If called without an argument, it kills from dot to the end
+of the line, unless it is at the end of the line, when it kills the
+newline.
+If called with an argument of 0, it kills from the start of the
+line to dot.
+If called with a positive argument, it kills from dot
+forward over that number of newlines.
+If called with a negative argument
+it kills any text before dot on the current line, then it kills back
+abs(n) lines.
+.It kill-paragraph
+Delete
+.Va n
+paragraphs starting with the current one.
+.It kill-region
+Kill the currently defined region.
+.It kill-word
+Delete forward
+.Va n
+words.
+.It leave-tmpdir-backups
+Modifies the behaviour of backup-to-home-directory.
+Backup files that would normally reside in the system
+.Ev TMPDIR
+are left there and not moved to the
+.Pa ~/.mg.d
+directory.
+.It line-number-mode
+Toggle whether line and column numbers are displayed in the modeline.
+.It list-buffers
+Display the list of available buffers.
+.It load
+Prompt the user for a filename, and then execute commands
+from that file.
+.It local-set-key
+Bind a key mapping in the local (topmost) mode.
+.It local-unset-key
+Unbind a key mapping in the local (topmost) mode.
+.It make-backup-files
+Toggle generation of backup files.
+.It mark-whole-buffer
+Marks whole buffer as a region by putting dot at the beginning and mark
+at the end of buffer.
+.It meta-key-mode
+When disabled, the meta key can be used to insert extended-ascii (8-bit)
+characters.
+When enabled, the meta key acts as usual.
+.It negative-argument
+Process a negative argument for keyboard-invoked functions.
+.It newline
+Insert a newline into the current buffer.
+.It newline-and-indent
+Insert a newline, then enough tabs and spaces to duplicate the indentation
+of the previous line.
+Assumes tabs are every eight characters.
+.It next-line
+Move forward
+.Va n
+lines.
+.It no-tab-mode
+Toggle notab mode.
+In this mode, spaces are inserted rather than tabs.
+.It not-modified
+Turn off the modified flag in the current buffer.
+.It open-line
+Open up some blank space.
+Essentially, insert
+.Va n
+newlines, then back up over them.
+.It other-window
+The command to make the next (down the screen) window the current
+window.
+There are no real errors, although the command does nothing if
+there is only 1 window on the screen.
+.It overwrite-mode
+Toggle overwrite mode, where typing in a buffer overwrites
+existing characters rather than inserting them.
+.It prefix-region
+Inserts a prefix string before each line of a region.
+The prefix string is settable by using 'set-prefix-string'.
+.It previous-line
+Move backwards
+.Va n
+lines.
+.It previous-window
+This command makes the previous (up the screen) window the
+current window.
+There are no errors, although the command does not do
+a lot if there is only 1 window.
+.It pop-tag-mark
+Return to position where find-tag was previously invoked.
+.It push-shell
+Suspend
+.Nm
+and switch to alternate screen, if available.
+.It pwd
+Display current (global) working directory in the status area.
+.It query-replace
+Query Replace.
+Search and replace strings selectively, prompting after each match.
+.It replace-string
+Replace string globally without individual prompting.
+.It query-replace-regexp
+Replace strings selectively.
+Does a search and replace operation using regular
+expressions for both patterns.
+.It quoted-insert
+Insert the next character verbatim into the current buffer; i.e. ignore
+any function bound to that key.
+.It re-search-again
+Perform a regular expression search again, using the same search
+string and direction as the last search command.
+.It re-search-backward
+Search backwards using a regular expression.
+Get a search string from the user, and search, starting at dot
+and proceeding toward the front of the buffer.
+If found, dot is left
+pointing at the first character of the pattern [the last character that
+was matched].
+.It re-search-forward
+Search forward using a regular expression.
+Get a search string from the user and search for it starting at dot.
+If found, move dot to just after the matched characters.
+display does all
+the hard stuff.
+If not found, it just prints a message.
+.It recenter
+Reposition dot in the current window.
+By default, the dot is centered.
+If given a positive argument (n), the display is repositioned to line
+n.
+If
+.Va n
+is negative, it is that line from the bottom.
+.It redraw-display
+Refresh the display.
+Recomputes all window sizes in case something has changed.
+.It save-buffer
+Save the contents of the current buffer if it has been changed,
+optionally creating a backup copy.
+.It save-buffers-kill-emacs
+Offer to save modified buffers and quit
+.Nm .
+.It save-some-buffers
+Look through the list of buffers, offering to save any buffer that
+has been changed.
+Buffers that are not associated with files (such
+as *scratch*, *grep*, *compile*) are ignored.
+.It scroll-down
+Scroll backwards
+.Va n
+pages.
+A two-line overlap between pages is
+assumed.
+If given a repeat argument, scrolls back lines, not pages.
+.It scroll-one-line-down
+Scroll the display down
+.Va n
+lines without changing the cursor position.
+.It scroll-one-line-up
+Scroll the display
+.Va n
+lines up without moving the cursor position.
+.It scroll-other-window
+Scroll the next window in the window list window forward
+.Va n
+pages.
+.It scroll-up
+Scroll forward one page.
+A two-line overlap between pages is
+assumed.
+If given a repeat argument, scrolls back lines, not pages.
+.It search-again
+Search again, using the same search string and direction as the last
+search command.
+.It search-backward
+Reverse search.
+Get a search string from the user, and search, starting
+at dot and proceeding toward the front of the buffer.
+If found, dot is
+left pointing at the first character of the pattern (the last character
+that was matched).
+.It search-forward
+Search forward.
+Get a search string from the user, and search for it
+starting at dot.
+If found, dot gets moved to just after the matched
+characters, if not found, print a message.
+.It self-insert-command
+Insert a character.
+.It set-case-fold-search
+Set case-fold searching, causing case not to matter
+in regular expression searches.
+This is the default.
+.It set-default-mode
+Append the supplied mode to the list of default modes
+used by subsequent buffer creation.
+Built in modes include: fill, indent, overwrite, and notab.
+.It set-fill-column
+Prompt the user for a fill column.
+Used by auto-fill-mode.
+.It set-mark-command
+Sets the mark in the current window to the current dot location.
+.It set-prefix-string
+Sets the prefix string to be used by the 'prefix-region' command.
+.It shell-command-on-region
+Provide the text in region to the shell command as input.
+.It shrink-window
+Shrink current window by one line.
+The window immediately below is expanded to pick up the slack.
+If only one window is present, this command has no effect.
+.It space-to-tabstop
+Insert enough spaces to reach the next tab-stop position.
+By default, tab-stops occur every 8 characters.
+.It split-window-vertically
+Split the current window.
+A window smaller than 3 lines cannot be split.
+.It start-kbd-macro
+Start defining a keyboard macro.
+Macro definition is ended by invoking end-kbd-macro.
+.It suspend-emacs
+Suspend
+.Nm
+and switch back to alternate screen, if in use.
+.It switch-to-buffer
+Prompt and switch to a new buffer in the current window.
+.It switch-to-buffer-other-window
+Switch to buffer in another window.
+.It toggle-read-only
+Toggle the read-only flag on the current buffer.
+.It transpose-chars
+Transpose the two characters on either side of dot.
+If dot is at the end of the line, transpose the two characters before it.
+Return with an error if dot is at the beginning of line;
+it seems to be a bit pointless to
+make this work.
+.It undo
+Undo the most recent action.
+If invoked again without an intervening command,
+move the undo pointer to the previous action and undo it.
+.It undo-boundary
+Add an undo boundary.
+This is not usually done interactively.
+.It undo-boundary-toggle
+Toggle whether undo boundaries are generated.
+Undo boundaries are often disabled before operations that should
+be considered atomically undoable.
+.It undo-enable
+Toggle whether undo information is kept.
+.It undo-list
+Show the undo records for the current buffer in a new buffer.
+.It universal-argument
+Repeat the next command 4 times.
+Usually bound to C-u.
+This command may be stacked; e.g.\&
+C-u C-u C-f moves the cursor forward 16 characters.
+.It upcase-region
+Upper case region.
+Change all of the lower case characters in the region to
+upper case.
+.It upcase-word
+Move the cursor forward by the specified number of words.
+As it moves, convert any characters to upper case.
+.It visit-tags-table
+Record name of the tags file to be used for subsequent find-tag.
+.It what-cursor-position
+Display a bunch of useful information about the current location of
+dot.
+The character under the cursor (in octal), the current line, row,
+and column, and approximate position of the cursor in the file (as a
+percentage) is displayed.
+The column position assumes an infinite
+position display; it does not truncate just because the screen does.
+.It write-file
+Ask for a file name and write the contents of the current buffer to
+that file.
+Update the remembered file name and clear the buffer
+changed flag.
+.It yank
+Yank text from kill-buffer.
+Unlike emacs, the
+.Nm
+kill buffer consists only
+of the most recent kill.
+It is not a ring.
+.El
+.Sh CONFIGURATION FILES
+There are two configuration files,
+.Pa .mg
+and
+.Pa .mg-TERM .
+Here,
+.Ev TERM
+represents the name of the terminal type; e.g. if the terminal type
+is set to
+.Dq vt100 ,
+.Nm
+will use
+.Pa .mg-vt100
+as a startup file.
+The terminal type startup file is used first.
+.Pp
+The startup file format is a list of commands, one per line, as used for
+interactive evaluation.
+Strings that are normally entered by the user at any subsequent prompts
+may be specified after the command name; e.g.:
+.Bd -literal -offset indent
+global-set-key ")" self-insert-command
+global-set-key "\e^x\e^f" find-file
+global-set-key "\ee[Z" backward-char
+set-default-mode fill
+set-fill-column 72
+auto-execute *.c c-mode
+.Ed
+.Sh FILES
+.Bl -tag -width /usr/share/doc/mg/tutorial -compact
+.It Pa ~/.mg
+normal startup file
+.It Pa ~/.mg-TERM
+terminal-specific startup file
+.It Pa ~/.mg.d
+alternative backup file location
+.It Pa /usr/share/doc/mg/tutorial
+concise tutorial
+.El
+.Sh SEE ALSO
+.Xr ctags 1 ,
+.Xr vi 1
+.Sh CAVEATS
+Since it is written completely in C, there is currently no
+language in which extensions can be written;
+however, keys can be rebound and certain parameters can be changed
+in startup files.
+.Pp
+In order to use 8-bit characters (such as German umlauts), the Meta key
+needs to be disabled via the
+.Dq meta-key-mode
+command.
+
diff --git a/mg/modes.c b/mg/modes.c
new file mode 100644 (file)
index 0000000..cde8728
--- /dev/null
@@ -0,0 +1,165 @@
+/*     $OpenBSD: modes.c,v 1.18 2008/06/14 08:46:30 kjell Exp $        */
+
+/* This file is in the public domain. */
+
+/*
+ * Commands to toggle modes.   Without an argument, these functions will
+ * toggle the given mode.  A negative or zero argument will turn the mode
+ * off.  A positive argument will turn the mode on.
+ */
+
+#include "def.h"
+#include "kbd.h"
+
+int    changemode(int, int, char *);
+
+int     defb_nmodes = 0;
+struct maps_s  *defb_modes[PBMODES] = { &fundamental_mode };
+int     defb_flag = 0;
+
+int
+changemode(int f, int n, char *mode)
+{
+       int      i;
+       struct maps_s   *m;
+
+       if ((m = name_mode(mode)) == NULL) {
+               ewprintf("Can't find mode %s", mode);
+               return (FALSE);
+       }
+       if (!(f & FFARG)) {
+               for (i = 0; i <= curbp->b_nmodes; i++)
+                       if (curbp->b_modes[i] == m) {
+                               /* mode already set */
+                               n = 0;
+                               break;
+                       }
+       }
+       if (n > 0) {
+               for (i = 0; i <= curbp->b_nmodes; i++)
+                       if (curbp->b_modes[i] == m)
+                               /* mode already set */
+                               return (TRUE);
+               if (curbp->b_nmodes >= PBMODES - 1) {
+                       ewprintf("Too many modes");
+                       return (FALSE);
+               }
+               curbp->b_modes[++(curbp->b_nmodes)] = m;
+       } else {
+               /* fundamental is b_modes[0] and can't be unset */
+               for (i = 1; i <= curbp->b_nmodes && m != curbp->b_modes[i];
+                   i++);
+               if (i > curbp->b_nmodes)
+                       return (TRUE);  /* mode wasn't set */
+               for (; i < curbp->b_nmodes; i++)
+                       curbp->b_modes[i] = curbp->b_modes[i + 1];
+               curbp->b_nmodes--;
+       }
+       upmodes(curbp);
+       return (TRUE);
+}
+
+int
+indentmode(int f, int n)
+{
+       return (changemode(f, n, "indent"));
+}
+
+int
+fillmode(int f, int n)
+{
+       return (changemode(f, n, "fill"));
+}
+
+#ifdef NOTAB
+int
+notabmode(int f, int n)
+{
+       if (changemode(f, n, "notab") == FALSE)
+               return (FALSE);
+       if (f & FFARG) {
+               if (n <= 0)
+                       curbp->b_flag &= ~BFNOTAB;
+               else
+                       curbp->b_flag |= BFNOTAB;
+       } else
+               curbp->b_flag ^= BFNOTAB;
+       return (TRUE);
+}
+#endif /* NOTAB */
+
+int
+overwrite_mode(int f, int n)
+{
+       if (changemode(f, n, "overwrite") == FALSE)
+               return (FALSE);
+       if (f & FFARG) {
+               if (n <= 0)
+                       curbp->b_flag &= ~BFOVERWRITE;
+               else
+                       curbp->b_flag |= BFOVERWRITE;
+       } else
+               curbp->b_flag ^= BFOVERWRITE;
+       return (TRUE);
+}
+
+int
+set_default_mode(int f, int n)
+{
+       int      i;
+       struct maps_s   *m;
+       char     modebuf[32], *bufp;
+
+       if ((bufp = eread("Set Default Mode: ", modebuf, sizeof(modebuf),
+           EFNEW)) == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               return (FALSE);
+       if ((m = name_mode(modebuf)) == NULL) {
+               ewprintf("can't find mode %s", modebuf);
+               return (FALSE);
+       }
+       if (!(f & FFARG)) {
+               for (i = 0; i <= defb_nmodes; i++)
+                       if (defb_modes[i] == m) {
+                               /* mode already set */
+                               n = 0;
+                               break;
+                       }
+       }
+       if (n > 0) {
+               for (i = 0; i <= defb_nmodes; i++)
+                       if (defb_modes[i] == m)
+                               /* mode already set */
+                               return (TRUE);
+               if (defb_nmodes >= PBMODES - 1) {
+                       ewprintf("Too many modes");
+                       return (FALSE);
+               }
+               defb_modes[++defb_nmodes] = m;
+       } else {
+               /* fundamental is defb_modes[0] and can't be unset */
+               for (i = 1; i <= defb_nmodes && m != defb_modes[i]; i++);
+               if (i > defb_nmodes)
+                       /* mode was not set */
+                       return (TRUE);
+               for (; i < defb_nmodes; i++)
+                       defb_modes[i] = defb_modes[i + 1];
+               defb_nmodes--;
+       }
+       if (strcmp(modebuf, "overwrite") == 0) {
+               if (n <= 0)
+                       defb_flag &= ~BFOVERWRITE;
+               else
+                       defb_flag |= BFOVERWRITE;
+       }
+#ifdef NOTAB
+       if (strcmp(modebuf, "notab") == 0) {
+               if (n <= 0)
+                       defb_flag &= ~BFNOTAB;
+               else
+                       defb_flag |= BFNOTAB;
+       }
+#endif /* NOTAB */
+       return (TRUE);
+}
diff --git a/mg/paragraph.c b/mg/paragraph.c
new file mode 100644 (file)
index 0000000..57ce412
--- /dev/null
@@ -0,0 +1,362 @@
+/*     $OpenBSD: paragraph.c,v 1.22 2011/11/29 05:59:54 lum Exp $      */
+
+/* This file is in the public domain. */
+
+/*
+ * Code for dealing with paragraphs and filling. Adapted from MicroEMACS 3.6
+ * and GNU-ified by mwm@ucbvax.         Several bug fixes by blarson@usc-oberon.
+ */
+
+#include "def.h"
+
+static int     fillcol = 70;
+
+#define MAXWORD 256
+
+/*
+ * Move to start of paragraph.  Go back to the beginning of the current
+ * paragraph here we look for a <NL><NL> or <NL><TAB> or <NL><SPACE>
+ * combination to delimit the beginning of a paragraph.
+ */
+/* ARGSUSED */
+int
+gotobop(int f, int n)
+{
+       /* the other way... */
+       if (n < 0)
+               return (gotoeop(f, -n));
+
+       while (n-- > 0) {
+               /* first scan back until we are in a word */
+               while (backchar(FFRAND, 1) && inword() == 0);
+
+               /* and go to the B-O-Line */
+               curwp->w_doto = 0;
+
+               /*
+                * and scan back until we hit a <NL><SP> <NL><TAB> or
+                * <NL><NL>
+                */
+               while (lback(curwp->w_dotp) != curbp->b_headp)
+                       if (llength(lback(curwp->w_dotp)) &&
+                           lgetc(curwp->w_dotp, 0) != ' ' &&
+                           lgetc(curwp->w_dotp, 0) != '.' &&
+                           lgetc(curwp->w_dotp, 0) != '\t')
+                               curwp->w_dotp = lback(curwp->w_dotp);
+                       else {
+                               if (llength(lback(curwp->w_dotp)) &&
+                                   lgetc(curwp->w_dotp, 0) == '.') {
+                                       curwp->w_dotp = lforw(curwp->w_dotp);
+                                       if (curwp->w_dotp == curbp->b_headp) {
+                                               /*
+                                                * beyond end of buffer,
+                                                * cleanup time
+                                                */
+                                               curwp->w_dotp =
+                                                   lback(curwp->w_dotp);
+                                               curwp->w_doto =
+                                                   llength(curwp->w_dotp);
+                                       }
+                               }
+                               break;
+                       }
+       }
+       /* force screen update */
+       curwp->w_rflag |= WFMOVE;
+       return (TRUE);
+}
+
+/*
+ * Move to end of paragraph.  Go forward to the end of the current paragraph
+ * here we look for a <NL><NL> or <NL><TAB> or <NL><SPACE> combination to
+ * delimit the beginning of a paragraph.
+ */
+/* ARGSUSED */
+int
+gotoeop(int f, int n)
+{
+       /* the other way... */
+       if (n < 0)
+               return (gotobop(f, -n));
+
+       /* for each one asked for */
+       while (n-- > 0) {
+               /* Find the first word on/after the current line */
+               curwp->w_doto = 0;
+               while (forwchar(FFRAND, 1) && inword() == 0);
+
+               curwp->w_doto = 0;
+               curwp->w_dotp = lforw(curwp->w_dotp);
+
+               /* and scan forword until we hit a <NL><SP> or ... */
+               while (curwp->w_dotp != curbp->b_headp) {
+                       if (llength(curwp->w_dotp) &&
+                           lgetc(curwp->w_dotp, 0) != ' ' &&
+                           lgetc(curwp->w_dotp, 0) != '.' &&
+                           lgetc(curwp->w_dotp, 0) != '\t')
+                               curwp->w_dotp = lforw(curwp->w_dotp);
+                       else
+                               break;
+               }
+               if (curwp->w_dotp == curbp->b_headp) {
+                       /* beyond end of buffer, cleanup time */
+                       curwp->w_dotp = lback(curwp->w_dotp);
+                       curwp->w_doto = llength(curwp->w_dotp);
+                       break;
+               }
+       }
+       /* force screen update */
+       curwp->w_rflag |= WFMOVE;
+       return (TRUE);
+}
+
+/*
+ * Justify a paragraph.  Fill the current paragraph according to the current
+ * fill column.
+ */
+/* ARGSUSED */
+int
+fillpara(int f, int n)
+{
+       int      c;             /* current char during scan             */
+       int      wordlen;       /* length of current word               */
+       int      clength;       /* position on line during fill         */
+       int      i;             /* index during word copy               */
+       int      eopflag;       /* Are we at the End-Of-Paragraph?      */
+       int      firstflag;     /* first word? (needs no space)         */
+       int      newlength;     /* tentative new line length            */
+       int      eolflag;       /* was at end of line                   */
+       int      retval;        /* return value                         */
+       struct line     *eopline;       /* pointer to line just past EOP        */
+       char     wbuf[MAXWORD]; /* buffer for current word              */
+
+       undo_boundary_enable(FFRAND, 0);
+
+       /* record the pointer to the line just past the EOP */
+       (void)gotoeop(FFRAND, 1);
+       if (curwp->w_doto != 0) {
+               /* paragraph ends at end of buffer */
+               (void)lnewline();
+               eopline = lforw(curwp->w_dotp);
+       } else
+               eopline = curwp->w_dotp;
+
+       /* and back top the begining of the paragraph */
+       (void)gotobop(FFRAND, 1);
+
+       /* initialize various info */
+       while (inword() == 0 && forwchar(FFRAND, 1));
+
+       clength = curwp->w_doto;
+       wordlen = 0;
+
+       /* scan through lines, filling words */
+       firstflag = TRUE;
+       eopflag = FALSE;
+       while (!eopflag) {
+
+               /* get the next character in the paragraph */
+               if ((eolflag = (curwp->w_doto == llength(curwp->w_dotp)))) {
+                       c = ' ';
+                       if (lforw(curwp->w_dotp) == eopline)
+                               eopflag = TRUE;
+               } else
+                       c = lgetc(curwp->w_dotp, curwp->w_doto);
+
+               /* and then delete it */
+               if (ldelete((RSIZE) 1, KNONE) == FALSE && !eopflag) {
+                       retval = FALSE;
+                       goto cleanup;
+               }
+
+               /* if not a separator, just add it in */
+               if (c != ' ' && c != '\t') {
+                       if (wordlen < MAXWORD - 1)
+                               wbuf[wordlen++] = c;
+                       else {
+                               /*
+                                * You lose chars beyond MAXWORD if the word
+                                * is too long. I'm too lazy to fix it now; it
+                                * just silently truncated the word before,
+                                * so I get to feel smug.
+                                */
+                               ewprintf("Word too long!");
+                       }
+               } else if (wordlen) {
+
+                       /* calculate tentative new length with word added */
+                       newlength = clength + 1 + wordlen;
+
+                       /*
+                        * if at end of line or at doublespace and previous
+                        * character was one of '.','?','!' doublespace here.
+                        * behave the same way if a ')' is preceded by a
+                        * [.?!] and followed by a doublespace.
+                        */
+                       if ((eolflag ||
+                           curwp->w_doto == llength(curwp->w_dotp) ||
+                           (c = lgetc(curwp->w_dotp, curwp->w_doto)) == ' '
+                           || c == '\t') && (ISEOSP(wbuf[wordlen - 1]) ||
+                           (wbuf[wordlen - 1] == ')' && wordlen >= 2 &&
+                           ISEOSP(wbuf[wordlen - 2]))) &&
+                           wordlen < MAXWORD - 1)
+                               wbuf[wordlen++] = ' ';
+
+                       /* at a word break with a word waiting */
+                       if (newlength <= fillcol) {
+                               /* add word to current line */
+                               if (!firstflag) {
+                                       (void)linsert(1, ' ');
+                                       ++clength;
+                               }
+                               firstflag = FALSE;
+                       } else {
+                               if (curwp->w_doto > 0 &&
+                                   lgetc(curwp->w_dotp, curwp->w_doto - 1) == ' ') {
+                                       curwp->w_doto -= 1;
+                                       (void)ldelete((RSIZE) 1, KNONE);
+                               }
+                               /* start a new line */
+                               (void)lnewline();
+                               clength = 0;
+                       }
+
+                       /* and add the word in in either case */
+                       for (i = 0; i < wordlen; i++) {
+                               (void)linsert(1, wbuf[i]);
+                               ++clength;
+                       }
+                       wordlen = 0;
+               }
+       }
+       /* and add a last newline for the end of our new paragraph */
+       (void)lnewline();
+
+       /*
+        * We really should wind up where we started, (which is hard to keep
+        * track of) but I think the end of the last line is better than the
+        * beginning of the blank line.
+        */
+       (void)backchar(FFRAND, 1);
+       retval = TRUE;
+cleanup:
+       undo_boundary_enable(FFRAND, 1);
+       return (retval);
+}
+
+/*
+ * Delete a paragraph.  Delete n paragraphs starting with the current one.
+ */
+/* ARGSUSED */
+int
+killpara(int f, int n)
+{
+       int     status;         /* returned status of functions */
+
+       /* for each paragraph to delete */
+       while (n--) {
+
+               /* mark out the end and beginning of the para to delete */
+               (void)gotoeop(FFRAND, 1);
+
+               /* set the mark here */
+               curwp->w_markp = curwp->w_dotp;
+               curwp->w_marko = curwp->w_doto;
+
+               /* go to the beginning of the paragraph */
+               (void)gotobop(FFRAND, 1);
+
+               /* force us to the beginning of line */
+               curwp->w_doto = 0;
+
+               /* and delete it */
+               if ((status = killregion(FFRAND, 1)) != TRUE)
+                       return (status);
+
+               /* and clean up the 2 extra lines */
+               (void)ldelete((RSIZE) 1, KFORW);
+       }
+       return (TRUE);
+}
+
+/*
+ * Insert char with work wrap.  Check to see if we're past fillcol, and if so,
+ * justify this line.  As a last step, justify the line.
+ */
+/* ARGSUSED */
+int
+fillword(int f, int n)
+{
+       char    c;
+       int     col, i, nce;
+
+       for (i = col = 0; col <= fillcol; ++i, ++col) {
+               if (i == curwp->w_doto)
+                       return selfinsert(f, n);
+               c = lgetc(curwp->w_dotp, i);
+               if (c == '\t'
+#ifdef NOTAB
+                   && !(curbp->b_flag & BFNOTAB)
+#endif
+                       )
+                       col |= 0x07;
+               else if (ISCTRL(c) != FALSE)
+                       ++col;
+       }
+       if (curwp->w_doto != llength(curwp->w_dotp)) {
+               (void)selfinsert(f, n);
+               nce = llength(curwp->w_dotp) - curwp->w_doto;
+       } else
+               nce = 0;
+       curwp->w_doto = i;
+
+       if ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && c != '\t')
+               do {
+                       (void)backchar(FFRAND, 1);
+               } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
+                   c != '\t' && curwp->w_doto > 0);
+
+       if (curwp->w_doto == 0)
+               do {
+                       (void)forwchar(FFRAND, 1);
+               } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
+                   c != '\t' && curwp->w_doto < llength(curwp->w_dotp));
+
+       (void)delwhite(FFRAND, 1);
+       (void)lnewline();
+       i = llength(curwp->w_dotp) - nce;
+       curwp->w_doto = i > 0 ? i : 0;
+       curwp->w_rflag |= WFMOVE;
+       if (nce == 0 && curwp->w_doto != 0)
+               return (fillword(f, n));
+       return (TRUE);
+}
+
+/*
+ * Set fill column to n for justify.
+ */
+int
+setfillcol(int f, int n)
+{
+       char buf[32], *rep;
+       const char *es;
+       int nfill;
+
+       if ((f & FFARG) != 0) {
+               fillcol = n;
+       } else {
+               if ((rep = eread("Set fill-column: ", buf, sizeof(buf),
+                   EFNEW | EFCR)) == NULL)
+                       return (ABORT);
+               else if (rep[0] == '\0')
+                       return (FALSE);
+               nfill = strtonum(rep, 0, INT_MAX, &es);
+               if (es != NULL) {
+                       ewprintf("Invalid fill column: %s", rep);
+                       return (FALSE);
+               }
+               fillcol = nfill;
+               ewprintf("Fill column set to %d", fillcol);
+       }
+       return (TRUE);
+}
diff --git a/mg/pathnames.h b/mg/pathnames.h
new file mode 100644 (file)
index 0000000..9720076
--- /dev/null
@@ -0,0 +1,11 @@
+/*     $OpenBSD: pathnames.h,v 1.1 2012/06/18 07:14:55 jasper Exp $    */
+
+/* This file is in the public domain. */
+
+/*
+ *     standard path names
+ */
+
+#define        _PATH_MG_DIR            "~/.mg.d"
+#define        _PATH_MG_STARTUP        "%s/.mg"
+#define        _PATH_MG_TERM           "%s/.mg-%s"
diff --git a/mg/random.c b/mg/random.c
new file mode 100644 (file)
index 0000000..27fd0c3
--- /dev/null
@@ -0,0 +1,496 @@
+/*     $OpenBSD: random.c,v 1.31 2012/05/18 02:13:44 lum Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ *             Assorted commands.
+ * This file contains the command processors for a large assortment of
+ * unrelated commands.  The only thing they have in common is that they
+ * are all command processors.
+ */
+
+#include "def.h"
+
+#include <ctype.h>
+
+/*
+ * Display a bunch of useful information about the current location of dot.
+ * The character under the cursor (in octal), the current line, row, and
+ * column, and approximate position of the cursor in the file (as a
+ * percentage) is displayed.  The column position assumes an infinite
+ * position display; it does not truncate just because the screen does.
+ * This is normally bound to "C-X =".
+ */
+/* ARGSUSED */
+int
+showcpos(int f, int n)
+{
+       struct line     *clp;
+       long     nchar, cchar;
+       int      nline, row;
+       int      cline, cbyte;          /* Current line/char/byte */
+       int      ratio;
+
+       /* collect the data */
+       clp = bfirstlp(curbp);
+       cchar = 0;
+       cline = 0;
+       cbyte = 0;
+       nchar = 0;
+       nline = 0;
+       for (;;) {
+               /* count this line */
+               ++nline;
+               if (clp == curwp->w_dotp) {
+                       /* mark line */
+                       cline = nline;
+                       cchar = nchar + curwp->w_doto;
+                       if (curwp->w_doto == llength(clp))
+                               cbyte = '\n';
+                       else
+                               cbyte = lgetc(clp, curwp->w_doto);
+               }
+               /* now count the chars */
+               nchar += llength(clp);
+               clp = lforw(clp);
+               if (clp == curbp->b_headp)
+                       break;
+               /* count the newline */
+               nchar++;
+       }
+       /* determine row */
+       row = curwp->w_toprow + 1;
+       clp = curwp->w_linep;
+       while (clp != curbp->b_headp && clp != curwp->w_dotp) {
+               ++row;
+               clp = lforw(clp);
+       }
+       /* NOSTRICT */
+       ratio = nchar ? (100L * cchar) / nchar : 100;
+       ewprintf("Char: %c (0%o)  point=%ld(%d%%)  line=%d  row=%d  col=%d",
+           cbyte, cbyte, cchar, ratio, cline, row, getcolpos());
+       return (TRUE);
+}
+
+int
+getcolpos(void)
+{
+       int     col, i, c;
+       char tmp[5];
+
+       /* determine column */
+       col = 0;
+
+       for (i = 0; i < curwp->w_doto; ++i) {
+               c = lgetc(curwp->w_dotp, i);
+               if (c == '\t'
+#ifdef NOTAB
+                   && !(curbp->b_flag & BFNOTAB)
+#endif /* NOTAB */
+                       ) {
+                       col |= 0x07;
+                       col++;
+               } else if (ISCTRL(c) != FALSE)
+                       col += 2;
+               else if (isprint(c)) {
+                       col++;
+               } else {
+                       col += snprintf(tmp, sizeof(tmp), "\\%o", c);
+               }
+
+       }
+       return (col);
+}
+
+/*
+ * Twiddle the two characters on either side of dot.  If dot is at the end
+ * of the line twiddle the two characters before it.  Return with an error
+ * if dot is at the beginning of line; it seems to be a bit pointless to
+ * make this work.  This fixes up a very common typo with a single stroke.
+ * Normally bound to "C-T".  This always works within a line, so "WFEDIT"
+ * is good enough.
+ */
+/* ARGSUSED */
+int
+twiddle(int f, int n)
+{
+       struct line     *dotp;
+       int      doto, cr;
+       int      fudge = FALSE;
+
+       dotp = curwp->w_dotp;
+       doto = curwp->w_doto;
+       if (doto == llength(dotp)) {
+               if (--doto <= 0)
+                       return (FALSE);
+               (void)backchar(FFRAND, 1);
+               fudge = TRUE;
+       } else {
+               if (doto == 0)
+                       return (FALSE);
+       }
+       undo_boundary_enable(FFRAND, 0);
+       cr = lgetc(dotp, doto - 1);
+       (void)backdel(FFRAND, 1);
+       (void)forwchar(FFRAND, 1);
+       linsert(1, cr);
+       if (fudge != TRUE)
+               (void)backchar(FFRAND, 1);
+       undo_boundary_enable(FFRAND, 1);
+       lchange(WFEDIT);
+       return (TRUE);
+}
+
+/*
+ * Open up some blank space.  The basic plan is to insert a bunch of
+ * newlines, and then back up over them.  Everything is done by the
+ * subcommand processors.  They even handle the looping.  Normally this
+ * is bound to "C-O".
+ */
+/* ARGSUSED */
+int
+openline(int f, int n)
+{
+       int     i, s;
+
+       if (n < 0)
+               return (FALSE);
+       if (n == 0)
+               return (TRUE);
+
+       /* insert newlines */
+       undo_boundary_enable(FFRAND, 0);
+       i = n;
+       do {
+               s = lnewline();
+       } while (s == TRUE && --i);
+
+       /* then go back up overtop of them all */
+       if (s == TRUE)
+               s = backchar(f | FFRAND, n);
+       undo_boundary_enable(FFRAND, 1);
+       return (s);
+}
+
+/*
+ * Insert a newline.
+ */
+/* ARGSUSED */
+int
+newline(int f, int n)
+{
+       int      s;
+
+       if (n < 0)
+               return (FALSE);
+
+       while (n--) {
+               if ((s = lnewline()) != TRUE)
+                       return (s);
+       }
+       return (TRUE);
+}
+
+/*
+ * Delete blank lines around dot. What this command does depends if dot is
+ * sitting on a blank line. If dot is sitting on a blank line, this command
+ * deletes all the blank lines above and below the current line. If it is
+ * sitting on a non blank line then it deletes all of the blank lines after
+ * the line. Normally this command is bound to "C-X C-O". Any argument is
+ * ignored.
+ */
+/* ARGSUSED */
+int
+deblank(int f, int n)
+{
+       struct line     *lp1, *lp2;
+       RSIZE    nld;
+
+       lp1 = curwp->w_dotp;
+       while (llength(lp1) == 0 && (lp2 = lback(lp1)) != curbp->b_headp)
+               lp1 = lp2;
+       lp2 = lp1;
+       nld = (RSIZE)0;
+       while ((lp2 = lforw(lp2)) != curbp->b_headp && llength(lp2) == 0)
+               ++nld;
+       if (nld == 0)
+               return (TRUE);
+       curwp->w_dotp = lforw(lp1);
+       curwp->w_doto = 0;
+       return (ldelete((RSIZE)nld, KNONE));
+}
+
+/*
+ * Delete any whitespace around dot, then insert a space.
+ */
+int
+justone(int f, int n)
+{
+       undo_boundary_enable(FFRAND, 0);
+       (void)delwhite(f, n);
+       linsert(1, ' ');
+       undo_boundary_enable(FFRAND, 1);
+       return (TRUE);
+}
+
+/*
+ * Delete any whitespace around dot.
+ */
+/* ARGSUSED */
+int
+delwhite(int f, int n)
+{
+       int     col, s;
+
+       col = curwp->w_doto;
+
+       while (col < llength(curwp->w_dotp) &&
+           (isspace(lgetc(curwp->w_dotp, col))))
+               ++col;
+       do {
+               if (curwp->w_doto == 0) {
+                       s = FALSE;
+                       break;
+               }
+               if ((s = backchar(FFRAND, 1)) != TRUE)
+                       break;
+       } while (isspace(lgetc(curwp->w_dotp, curwp->w_doto)));
+
+       if (s == TRUE)
+               (void)forwchar(FFRAND, 1);
+       (void)ldelete((RSIZE)(col - curwp->w_doto), KNONE);
+       return (TRUE);
+}
+
+/*
+ * Delete any leading whitespace on the current line
+ */
+int
+delleadwhite(int f, int n)
+{
+       int soff, ls;
+       struct line *slp;
+
+       /* Save current position */
+       slp = curwp->w_dotp;
+       soff = curwp->w_doto;
+
+       for (ls = 0; ls < llength(slp); ls++)
+                 if (!isspace(lgetc(slp, ls)))
+                        break;
+       gotobol(FFRAND, 1);
+       forwdel(FFRAND, ls);
+       soff -= ls;
+       if (soff < 0)
+               soff = 0;
+       forwchar(FFRAND, soff);
+
+       return (TRUE);
+}
+
+/*
+ * Delete any trailing whitespace on the current line
+ */
+int
+deltrailwhite(int f, int n)
+{
+       int soff;
+
+       /* Save current position */
+       soff = curwp->w_doto;
+
+       gotoeol(FFRAND, 1);
+       delwhite(FFRAND, 1);
+
+       /* restore original position, if possible */
+       if (soff < curwp->w_doto)
+               curwp->w_doto = soff;
+
+       return (TRUE);
+}
+
+
+
+/*
+ * Insert a newline, then enough tabs and spaces to duplicate the indentation
+ * of the previous line.  Assumes tabs are every eight characters.  Quite
+ * simple.  Figure out the indentation of the current line.  Insert a newline
+ * by calling the standard routine.  Insert the indentation by inserting the
+ * right number of tabs and spaces.  Return TRUE if all ok.  Return FALSE if
+ * one of the subcommands failed. Normally bound to "C-M".
+ */
+/* ARGSUSED */
+int
+lfindent(int f, int n)
+{
+       int     c, i, nicol;
+       int     s = TRUE;
+
+       if (n < 0)
+               return (FALSE);
+
+       undo_boundary_enable(FFRAND, 0);
+       while (n--) {
+               nicol = 0;
+               for (i = 0; i < llength(curwp->w_dotp); ++i) {
+                       c = lgetc(curwp->w_dotp, i);
+                       if (c != ' ' && c != '\t')
+                               break;
+                       if (c == '\t')
+                               nicol |= 0x07;
+                       ++nicol;
+               }
+               if (lnewline() == FALSE || ((
+#ifdef NOTAB
+                   curbp->b_flag & BFNOTAB) ? linsert(nicol, ' ') == FALSE : (
+#endif /* NOTAB */
+                   ((i = nicol / 8) != 0 && linsert(i, '\t') == FALSE) ||
+                   ((i = nicol % 8) != 0 && linsert(i, ' ') == FALSE)))) {
+                       s = FALSE;
+                       break;
+               }
+       }
+       undo_boundary_enable(FFRAND, 1);
+       return (s);
+}
+
+/*
+ * Indent the current line. Delete existing leading whitespace,
+ * and use tabs/spaces to achieve correct indentation. Try
+ * to leave dot where it started.
+ */
+int
+indent(int f, int n)
+{
+       int soff, i;
+
+       if (n < 0)
+               return (FALSE);
+
+       delleadwhite(FFRAND, 1);
+
+       /* If not invoked with a numerical argument, done */
+       if (!(f & FFARG))
+               return (TRUE);
+
+       /* insert appropriate whitespace */
+       soff = curwp->w_doto;
+       (void)gotobol(FFRAND, 1);
+       if (
+#ifdef NOTAB
+           (curbp->b_flag & BFNOTAB) ? linsert(n, ' ') == FALSE :
+#endif /* NOTAB */
+           (((i = n / 8) != 0 && linsert(i, '\t') == FALSE) ||
+           ((i = n % 8) != 0 && linsert(i, ' ') == FALSE)))
+               return (FALSE);
+
+       forwchar(FFRAND, soff);
+
+       return (TRUE);
+}
+
+
+/*
+ * Delete forward.  This is real easy, because the basic delete routine does
+ * all of the work.  Watches for negative arguments, and does the right thing.
+ * If any argument is present, it kills rather than deletes, to prevent loss
+ * of text if typed with a big argument.  Normally bound to "C-D".
+ */
+/* ARGSUSED */
+int
+forwdel(int f, int n)
+{
+       if (n < 0)
+               return (backdel(f | FFRAND, -n));
+
+       /* really a kill */
+       if (f & FFARG) {
+               if ((lastflag & CFKILL) == 0)
+                       kdelete();
+               thisflag |= CFKILL;
+       }
+
+       return (ldelete((RSIZE) n, (f & FFARG) ? KFORW : KNONE));
+}
+
+/*
+ * Delete backwards.  This is quite easy too, because it's all done with
+ * other functions.  Just move the cursor back, and delete forwards.  Like
+ * delete forward, this actually does a kill if presented with an argument.
+ */
+/* ARGSUSED */
+int
+backdel(int f, int n)
+{
+       int     s;
+
+       if (n < 0)
+               return (forwdel(f | FFRAND, -n));
+
+       /* really a kill */
+       if (f & FFARG) {
+               if ((lastflag & CFKILL) == 0)
+                       kdelete();
+               thisflag |= CFKILL;
+       }
+       if ((s = backchar(f | FFRAND, n)) == TRUE)
+               s = ldelete((RSIZE)n, (f & FFARG) ? KFORW : KNONE);
+
+       return (s);
+}
+
+#ifdef NOTAB
+/* ARGSUSED */
+int
+space_to_tabstop(int f, int n)
+{
+       if (n < 0)
+               return (FALSE);
+       if (n == 0)
+               return (TRUE);
+       return (linsert((n << 3) - (curwp->w_doto & 7), ' '));
+}
+#endif /* NOTAB */
+
+/*
+ * Move the dot to the first non-whitespace character of the current line.
+ */
+int
+backtoindent(int f, int n)
+{
+       gotobol(FFRAND, 1);
+       while (curwp->w_doto < llength(curwp->w_dotp) &&
+           (isspace(lgetc(curwp->w_dotp, curwp->w_doto))))
+               ++curwp->w_doto;
+       return (TRUE);
+}
+
+/*
+ * Join the current line to the previous, or with arg, the next line
+ * to the current one.  If the former line is not empty, leave exactly
+ * one space at the joint.  Otherwise, leave no whitespace.
+ */
+int
+joinline(int f, int n)
+{
+       int doto;
+
+       undo_boundary_enable(FFRAND, 0);
+       if (f & FFARG) {
+               gotoeol(FFRAND, 1);
+               forwdel(FFRAND, 1);
+       } else {
+               gotobol(FFRAND, 1);
+               backdel(FFRAND, 1);
+       }
+
+       delwhite(FFRAND, 1);
+
+       if ((doto = curwp->w_doto) > 0) {
+               linsert(1, ' ');
+               curwp->w_doto = doto;
+       }
+       undo_boundary_enable(FFRAND, 1);
+
+       return (TRUE);
+}
diff --git a/mg/re_search.c b/mg/re_search.c
new file mode 100644 (file)
index 0000000..db39fdc
--- /dev/null
@@ -0,0 +1,615 @@
+/*     $OpenBSD: re_search.c,v 1.26 2011/01/21 19:10:13 kjell Exp $    */
+
+/* This file is in the public domain. */
+
+/*
+ *     regular expression search commands for Mg
+ *
+ * This file contains functions to implement several of gnuemacs's regular
+ * expression functions for Mg.  Several of the routines below are just minor
+ * re-arrangements of Mg's non-regular expression search functions.  Some of
+ * them are similar in structure to the original MicroEMACS, others are
+ * modifications of Rich Ellison's code.  Peter Newton re-wrote about half of
+ * them from scratch.
+ */
+
+#ifdef REGEX
+#include "def.h"
+
+#include <sys/types.h>
+#include <regex.h>
+
+#include "macro.h"
+
+#define SRCH_BEGIN     (0)             /* search sub-codes                 */
+#define SRCH_FORW      (-1)
+#define SRCH_BACK      (-2)
+#define SRCH_NOPR      (-3)
+#define SRCH_ACCM      (-4)
+#define SRCH_MARK      (-5)
+
+#define RE_NMATCH      10              /* max number of matches            */
+#define REPLEN         256             /* max length of replacement string */
+
+char   re_pat[NPAT];                   /* regex pattern                    */
+int    re_srch_lastdir = SRCH_NOPR;    /* last search flags                */
+int    casefoldsearch = TRUE;          /* does search ignore case?         */
+
+static int      re_doreplace(RSIZE, char *);
+static int      re_forwsrch(void);
+static int      re_backsrch(void);
+static int      re_readpattern(char *);
+static int      killmatches(int);
+static int      countmatches(int);
+
+/*
+ * Search forward.
+ * Get a search string from the user and search for it starting at ".".  If
+ * found, move "." to just after the matched characters.  display does all
+ * the hard stuff.  If not found, it just prints a message.
+ */
+/* ARGSUSED */
+int
+re_forwsearch(int f, int n)
+{
+       int     s;
+
+       if ((s = re_readpattern("RE Search")) != TRUE)
+               return (s);
+       if (re_forwsrch() == FALSE) {
+               ewprintf("Search failed: \"%s\"", re_pat);
+               return (FALSE);
+       }
+       re_srch_lastdir = SRCH_FORW;
+       return (TRUE);
+}
+
+/*
+ * Reverse search.
+ * Get a search string from the user, and search, starting at "."
+ * and proceeding toward the front of the buffer. If found "." is left
+ * pointing at the first character of the pattern [the last character that
+ * was matched].
+ */
+/* ARGSUSED */
+int
+re_backsearch(int f, int n)
+{
+       int     s;
+
+       if ((s = re_readpattern("RE Search backward")) != TRUE)
+               return (s);
+       if (re_backsrch() == FALSE) {
+               ewprintf("Search failed: \"%s\"", re_pat);
+               return (FALSE);
+       }
+       re_srch_lastdir = SRCH_BACK;
+       return (TRUE);
+}
+
+/*
+ * Search again, using the same search string and direction as the last search
+ * command.  The direction has been saved in "srch_lastdir", so you know which
+ * way to go.
+ *
+ * XXX: This code has problems -- some incompatibility(?) with extend.c causes
+ * match to fail when it should not.
+ */
+/* ARGSUSED */
+int
+re_searchagain(int f, int n)
+{
+       if (re_srch_lastdir == SRCH_NOPR) {
+               ewprintf("No last search");
+               return (FALSE);
+       }
+       if (re_srch_lastdir == SRCH_FORW) {
+               if (re_forwsrch() == FALSE) {
+                       ewprintf("Search failed: \"%s\"", re_pat);
+                       return (FALSE);
+               }
+               return (TRUE);
+       }
+       if (re_srch_lastdir == SRCH_BACK)
+               if (re_backsrch() == FALSE) {
+                       ewprintf("Search failed: \"%s\"", re_pat);
+                       return (FALSE);
+               }
+
+       return (TRUE);
+}
+
+/* Compiled regex goes here-- changed only when new pattern read */
+static regex_t         re_buff;
+static regmatch_t      re_match[RE_NMATCH];
+
+/*
+ * Re-Query Replace.
+ *     Replace strings selectively.  Does a search and replace operation.
+ */
+/* ARGSUSED */
+int
+re_queryrepl(int f, int n)
+{
+       int     rcnt = 0;               /* replacements made so far     */
+       int     plen, s;                /* length of found string       */
+       char    news[NPAT];             /* replacement string           */
+
+       if ((s = re_readpattern("RE Query replace")) != TRUE)
+               return (s);
+       if (eread("Query replace %s with: ", news, NPAT,
+           EFNUL | EFNEW | EFCR, re_pat) == NULL)
+               return (ABORT);
+       ewprintf("Query replacing %s with %s:", re_pat, news);
+
+       /*
+        * Search forward repeatedly, checking each time whether to insert
+        * or not.  The "!" case makes the check always true, so it gets put
+        * into a tighter loop for efficiency.
+        */
+       while (re_forwsrch() == TRUE) {
+retry:
+               update();
+               switch (getkey(FALSE)) {
+               case ' ':
+                       plen = re_match[0].rm_eo - re_match[0].rm_so;
+                       if (re_doreplace((RSIZE)plen, news) == FALSE)
+                               return (FALSE);
+                       rcnt++;
+                       break;
+
+               case '.':
+                       plen = re_match[0].rm_eo - re_match[0].rm_so;
+                       if (re_doreplace((RSIZE)plen, news) == FALSE)
+                               return (FALSE);
+                       rcnt++;
+                       goto stopsearch;
+
+               case CCHR('G'):                         /* ^G */
+                       (void)ctrlg(FFRAND, 0);
+                       goto stopsearch;
+               case CCHR('['):                         /* ESC */
+               case '`':
+                       goto stopsearch;
+               case '!':
+                       do {
+                               plen = re_match[0].rm_eo - re_match[0].rm_so;
+                               if (re_doreplace((RSIZE)plen, news) == FALSE)
+                                       return (FALSE);
+                               rcnt++;
+                       } while (re_forwsrch() == TRUE);
+                       goto stopsearch;
+
+               case CCHR('?'):                         /* To not replace */
+                       break;
+
+               default:
+                       ewprintf("<SP> replace, [.] rep-end, <DEL> don't, [!] repl rest <ESC> quit");
+                       goto retry;
+               }
+       }
+
+stopsearch:
+       curwp->w_rflag |= WFFULL;
+       update();
+       if (!inmacro) {
+               if (rcnt == 0)
+                       ewprintf("(No replacements done)");
+               else if (rcnt == 1)
+                       ewprintf("(1 replacement done)");
+               else
+                       ewprintf("(%d replacements done)", rcnt);
+       }
+       return (TRUE);
+}
+
+/*
+ * Routine re_doreplace calls lreplace to make replacements needed by
+ * re_query replace.  Its reason for existence is to deal with \1, \2. etc.
+ *  plen: length to remove
+ *  st:   replacement string
+ */
+static int
+re_doreplace(RSIZE plen, char *st)
+{
+       int      j, k, s, more, num, state;
+       struct line     *clp;
+       char     repstr[REPLEN];
+
+       clp = curwp->w_dotp;
+       more = TRUE;
+       j = 0;
+       state = 0;
+       num = 0;
+
+       /* The following FSA parses the replacement string */
+       while (more) {
+               switch (state) {
+               case 0:
+                       if (*st == '\\') {
+                               st++;
+                               state = 1;
+                       } else if (*st == '\0')
+                               more = FALSE;
+                       else {
+                               repstr[j] = *st;
+                               j++;
+                               if (j >= REPLEN)
+                                       return (FALSE);
+                               st++;
+                       }
+                       break;
+               case 1:
+                       if (*st >= '0' && *st <= '9') {
+                               num = *st - '0';
+                               st++;
+                               state = 2;
+                       } else if (*st == '\0')
+                               more = FALSE;
+                       else {
+                               repstr[j] = *st;
+                               j++;
+                               if (j >= REPLEN)
+                                       return (FALSE);
+                               st++;
+                               state = 0;
+                       }
+                       break;
+               case 2:
+                       if (*st >= '0' && *st <= '9') {
+                               num = 10 * num + *st - '0';
+                               st++;
+                       } else {
+                               if (num >= RE_NMATCH)
+                                       return (FALSE);
+                               k = re_match[num].rm_eo - re_match[num].rm_so;
+                               if (j + k >= REPLEN)
+                                       return (FALSE);
+                               bcopy(&(clp->l_text[re_match[num].rm_so]),
+                                   &repstr[j], k);
+                               j += k;
+                               if (*st == '\0')
+                                       more = FALSE;
+                               if (*st == '\\') {
+                                       st++;
+                                       state = 1;
+                               } else {
+                                       repstr[j] = *st;
+                                       j++;
+                                       if (j >= REPLEN)
+                                               return (FALSE);
+                                       st++;
+                                       state = 0;
+                               }
+                       }
+                       break;
+               }               /* switch (state) */
+       }                       /* while (more)   */
+
+       repstr[j] = '\0';
+       s = lreplace(plen, repstr);
+       return (s);
+}
+
+/*
+ * This routine does the real work of a forward search.  The pattern is
+ * sitting in the external variable "pat".  If found, dot is updated, the
+ * window system is notified of the change, and TRUE is returned.  If the
+ * string isn't found, FALSE is returned.
+ */
+static int
+re_forwsrch(void)
+{
+       int      tbo, error;
+       struct line     *clp;
+
+       clp = curwp->w_dotp;
+       tbo = curwp->w_doto;
+
+       if (tbo == clp->l_used)
+               /*
+                * Don't start matching past end of line -- must move to
+                * beginning of next line, unless at end of file.
+                */
+               if (clp != curbp->b_headp) {
+                       clp = lforw(clp);
+                       tbo = 0;
+               }
+       /*
+        * Note this loop does not process the last line, but this editor
+        * always makes the last line empty so this is good.
+        */
+       while (clp != (curbp->b_headp)) {
+               re_match[0].rm_so = tbo;
+               re_match[0].rm_eo = llength(clp);
+               error = regexec(&re_buff, ltext(clp), RE_NMATCH, re_match,
+                   REG_STARTEND);
+               if (error != 0) {
+                       clp = lforw(clp);
+                       tbo = 0;
+               } else {
+                       curwp->w_doto = re_match[0].rm_eo;
+                       curwp->w_dotp = clp;
+                       curwp->w_rflag |= WFMOVE;
+                       return (TRUE);
+               }
+       }
+       return (FALSE);
+}
+
+/*
+ * This routine does the real work of a backward search.  The pattern is sitting
+ * in the external variable "re_pat".  If found, dot is updated, the window
+ * system is notified of the change, and TRUE is returned.  If the string isn't
+ * found, FALSE is returned.
+ */
+static int
+re_backsrch(void)
+{
+       struct line             *clp;
+       int              tbo;
+       regmatch_t       lastmatch;
+
+       clp = curwp->w_dotp;
+       tbo = curwp->w_doto;
+
+       /* Start search one position to the left of dot */
+       tbo = tbo - 1;
+       if (tbo < 0) {
+               /* must move up one line */
+               clp = lback(clp);
+               tbo = llength(clp);
+       }
+
+       /*
+        * Note this loop does not process the last line, but this editor
+        * always makes the last line empty so this is good.
+        */
+       while (clp != (curbp->b_headp)) {
+               re_match[0].rm_so = 0;
+               re_match[0].rm_eo = llength(clp);
+               lastmatch.rm_so = -1;
+               /*
+                * Keep searching until we don't match any longer.  Assumes a
+                * non-match does not modify the re_match array.  We have to
+                * do this character-by-character after the first match since
+                * POSIX regexps don't give you a way to do reverse matches.
+                */
+               while (!regexec(&re_buff, ltext(clp), RE_NMATCH, re_match,
+                   REG_STARTEND) && re_match[0].rm_so < tbo) {
+                       memcpy(&lastmatch, &re_match[0], sizeof(regmatch_t));
+                       re_match[0].rm_so++;
+                       re_match[0].rm_eo = llength(clp);
+               }
+               if (lastmatch.rm_so == -1) {
+                       clp = lback(clp);
+                       tbo = llength(clp);
+               } else {
+                       memcpy(&re_match[0], &lastmatch, sizeof(regmatch_t));
+                       curwp->w_doto = re_match[0].rm_so;
+                       curwp->w_dotp = clp;
+                       curwp->w_rflag |= WFMOVE;
+                       return (TRUE);
+               }
+       }
+       return (FALSE);
+}
+
+/*
+ * Read a pattern.
+ * Stash it in the external variable "re_pat". The "pat" is
+ * not updated if the user types in an empty line. If the user typed
+ * an empty line, and there is no old pattern, it is an error.
+ * Display the old pattern, in the style of Jeff Lomicka. There is
+ * some do-it-yourself control expansion.
+ */
+static int
+re_readpattern(char *prompt)
+{
+       static int      dofree = 0;
+       int             flags, error, s;
+       char            tpat[NPAT], *rep;
+
+       if (re_pat[0] == '\0')
+               rep = eread("%s: ", tpat, NPAT, EFNEW | EFCR, prompt);
+       else
+               rep = eread("%s: (default %s) ", tpat, NPAT,
+                   EFNUL | EFNEW | EFCR, prompt, re_pat);
+       if (rep == NULL)
+               return (ABORT);
+       if (rep[0] != '\0') {
+               /* New pattern given */
+               (void)strlcpy(re_pat, tpat, sizeof(re_pat));
+               if (casefoldsearch)
+                       flags = REG_EXTENDED | REG_ICASE;
+               else
+                       flags = REG_EXTENDED;
+               if (dofree)
+                       regfree(&re_buff);
+               error = regcomp(&re_buff, re_pat, flags);
+               if (error != 0) {
+                       char    message[256];
+                       regerror(error, &re_buff, message, sizeof(message));
+                       ewprintf("Regex Error: %s", message);
+                       re_pat[0] = '\0';
+                       return (FALSE);
+               }
+               dofree = 1;
+               s = TRUE;
+       } else if (rep[0] == '\0' && re_pat[0] != '\0')
+               /* Just using old pattern */
+               s = TRUE;
+       else
+               s = FALSE;
+       return (s);
+}
+
+/*
+ * Cause case to not matter in searches.  This is the default. If called
+ * with argument cause case to matter.
+ */
+/* ARGSUSED*/
+int
+setcasefold(int f, int n)
+{
+       if (f & FFARG) {
+               casefoldsearch = FALSE;
+               ewprintf("Case-fold-search unset");
+       } else {
+               casefoldsearch = TRUE;
+               ewprintf("Case-fold-search set");
+       }
+
+       /*
+        * Invalidate the regular expression pattern since I'm too lazy to
+        * recompile it.
+        */
+       re_pat[0] = '\0';
+       return (TRUE);
+}
+
+/*
+ * Delete all lines after dot that contain a string matching regex.
+ */
+/* ARGSUSED */
+int
+delmatchlines(int f, int n)
+{
+       int     s;
+
+       if ((s = re_readpattern("Flush lines (containing match for regexp)"))
+           != TRUE)
+               return (s);
+
+       s = killmatches(TRUE);
+       return (s);
+}
+
+/*
+ * Delete all lines after dot that don't contain a string matching regex.
+ */
+/* ARGSUSED */
+int
+delnonmatchlines(int f, int n)
+{
+       int     s;
+
+       if ((s = re_readpattern("Keep lines (containing match for regexp)"))
+           != TRUE)
+               return (s);
+
+       s = killmatches(FALSE);
+       return (s);
+}
+
+/*
+ * This function does the work of deleting matching lines.
+ */
+static int
+killmatches(int cond)
+{
+       int      s, error;
+       int      count = 0;
+       struct line     *clp;
+
+       clp = curwp->w_dotp;
+       if (curwp->w_doto == llength(clp))
+               /* Consider dot on next line */
+               clp = lforw(clp);
+
+       while (clp != (curbp->b_headp)) {
+               /* see if line matches */
+               re_match[0].rm_so = 0;
+               re_match[0].rm_eo = llength(clp);
+               error = regexec(&re_buff, ltext(clp), RE_NMATCH, re_match,
+                   REG_STARTEND);
+
+               /* Delete line when appropriate */
+               if ((cond == FALSE && error) || (cond == TRUE && !error)) {
+                       curwp->w_doto = 0;
+                       curwp->w_dotp = clp;
+                       count++;
+                       s = ldelete(llength(clp) + 1, KNONE);
+                       clp = curwp->w_dotp;
+                       curwp->w_rflag |= WFMOVE;
+                       if (s == FALSE)
+                               return (FALSE);
+               } else
+                       clp = lforw(clp);
+       }
+
+       ewprintf("%d line(s) deleted", count);
+       if (count > 0)
+               curwp->w_rflag |= WFMOVE;
+
+       return (TRUE);
+}
+
+/*
+ * Count lines matching regex.
+ */
+/* ARGSUSED */
+int
+cntmatchlines(int f, int n)
+{
+       int     s;
+
+       if ((s = re_readpattern("Count lines (matching regexp)")) != TRUE)
+               return (s);
+       s = countmatches(TRUE);
+
+       return (s);
+}
+
+/*
+ * Count lines that fail to match regex.
+ */
+/* ARGSUSED */
+int
+cntnonmatchlines(int f, int n)
+{
+       int     s;
+
+       if ((s = re_readpattern("Count lines (not matching regexp)")) != TRUE)
+               return (s);
+       s = countmatches(FALSE);
+
+       return (s);
+}
+
+/*
+ * This function does the work of counting matching lines.
+ */
+int
+countmatches(int cond)
+{
+       int      error;
+       int      count = 0;
+       struct line     *clp;
+
+       clp = curwp->w_dotp;
+       if (curwp->w_doto == llength(clp))
+               /* Consider dot on next line */
+               clp = lforw(clp);
+
+       while (clp != (curbp->b_headp)) {
+               /* see if line matches */
+               re_match[0].rm_so = 0;
+               re_match[0].rm_eo = llength(clp);
+               error = regexec(&re_buff, ltext(clp), RE_NMATCH, re_match,
+                   REG_STARTEND);
+
+               /* Count line when appropriate */
+               if ((cond == FALSE && error) || (cond == TRUE && !error))
+                       count++;
+               clp = lforw(clp);
+       }
+
+       if (cond)
+               ewprintf("Number of lines matching: %d", count);
+       else
+               ewprintf("Number of lines not matching: %d", count);
+
+       return (TRUE);
+}
+#endif /* REGEX */
diff --git a/mg/region.c b/mg/region.c
new file mode 100644 (file)
index 0000000..baca932
--- /dev/null
@@ -0,0 +1,597 @@
+/*     $OpenBSD: region.c,v 1.30 2012/04/11 17:51:10 lum Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ *             Region based commands.
+ * The routines in this file deal with the region, that magic space between
+ * "." and mark.  Some functions are commands.  Some functions are just for
+ * internal use.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <fcntl.h>
+#include <poll.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "def.h"
+
+#define TIMEOUT 10000
+
+static char leftover[BUFSIZ];
+
+static int     getregion(struct region *);
+static int     iomux(int);
+static int     pipeio(const char *);
+static int     preadin(int, struct buffer *);
+static void    pwriteout(int, char **, int *);
+static int     setsize(struct region *, RSIZE);
+
+/*
+ * Kill the region.  Ask "getregion" to figure out the bounds of the region.
+ * Move "." to the start, and kill the characters. Mark is cleared afterwards.
+ */
+/* ARGSUSED */
+int
+killregion(int f, int n)
+{
+       int     s;
+       struct region   region;
+
+       if ((s = getregion(&region)) != TRUE)
+               return (s);
+       /* This is a kill-type command, so do magic kill buffer stuff. */
+       if ((lastflag & CFKILL) == 0)
+               kdelete();
+       thisflag |= CFKILL;
+       curwp->w_dotp = region.r_linep;
+       curwp->w_doto = region.r_offset;
+       curwp->w_dotline = region.r_lineno;
+       s = ldelete(region.r_size, KFORW | KREG);
+       clearmark(FFARG, 0);
+
+       return (s);
+}
+
+/*
+ * Copy all of the characters in the region to the kill buffer,
+ * clearing the mark afterwards.
+ * This is a bit like a kill region followed by a yank.
+ */
+/* ARGSUSED */
+int
+copyregion(int f, int n)
+{
+       struct line     *linep;
+       struct region    region;
+       int      loffs;
+       int      s;
+
+       if ((s = getregion(&region)) != TRUE)
+               return (s);
+
+       /* kill type command */
+       if ((lastflag & CFKILL) == 0)
+               kdelete();
+       thisflag |= CFKILL;
+
+       /* current line */
+       linep = region.r_linep;
+
+       /* current offset */
+       loffs = region.r_offset;
+
+       while (region.r_size--) {
+               if (loffs == llength(linep)) {  /* End of line.          */
+                       if ((s = kinsert('\n', KFORW)) != TRUE)
+                               return (s);
+                       linep = lforw(linep);
+                       loffs = 0;
+               } else {                        /* Middle of line.       */
+                       if ((s = kinsert(lgetc(linep, loffs), KFORW)) != TRUE)
+                               return (s);
+                       ++loffs;
+               }
+       }
+       clearmark(FFARG, 0);
+
+       return (TRUE);
+}
+
+/*
+ * Lower case region.  Zap all of the upper case characters in the region to
+ * lower case. Use the region code to set the limits. Scan the buffer, doing
+ * the changes. Call "lchange" to ensure that redisplay is done in all
+ * buffers.
+ */
+/* ARGSUSED */
+int
+lowerregion(int f, int n)
+{
+       struct line     *linep;
+       struct region    region;
+       int      loffs, c, s;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read-only");
+               return (FALSE);
+       }
+
+       if ((s = getregion(&region)) != TRUE)
+               return (s);
+
+       undo_add_change(region.r_linep, region.r_offset, region.r_size);
+
+       lchange(WFFULL);
+       linep = region.r_linep;
+       loffs = region.r_offset;
+       while (region.r_size--) {
+               if (loffs == llength(linep)) {
+                       linep = lforw(linep);
+                       loffs = 0;
+               } else {
+                       c = lgetc(linep, loffs);
+                       if (ISUPPER(c) != FALSE)
+                               lputc(linep, loffs, TOLOWER(c));
+                       ++loffs;
+               }
+       }
+       return (TRUE);
+}
+
+/*
+ * Upper case region.  Zap all of the lower case characters in the region to
+ * upper case.  Use the region code to set the limits.  Scan the buffer,
+ * doing the changes.  Call "lchange" to ensure that redisplay is done in all
+ * buffers.
+ */
+/* ARGSUSED */
+int
+upperregion(int f, int n)
+{
+       struct line      *linep;
+       struct region     region;
+       int       loffs, c, s;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read-only");
+               return (FALSE);
+       }
+       if ((s = getregion(&region)) != TRUE)
+               return (s);
+
+       undo_add_change(region.r_linep, region.r_offset, region.r_size);
+
+       lchange(WFFULL);
+       linep = region.r_linep;
+       loffs = region.r_offset;
+       while (region.r_size--) {
+               if (loffs == llength(linep)) {
+                       linep = lforw(linep);
+                       loffs = 0;
+               } else {
+                       c = lgetc(linep, loffs);
+                       if (ISLOWER(c) != FALSE)
+                               lputc(linep, loffs, TOUPPER(c));
+                       ++loffs;
+               }
+       }
+       return (TRUE);
+}
+
+/*
+ * This routine figures out the bound of the region in the current window,
+ * and stores the results into the fields of the REGION structure. Dot and
+ * mark are usually close together, but I don't know the order, so I scan
+ * outward from dot, in both directions, looking for mark. The size is kept
+ * in a long. At the end, after the size is figured out, it is assigned to
+ * the size field of the region structure. If this assignment loses any bits,
+ * then we print an error. This is "type independent" overflow checking. All
+ * of the callers of this routine should be ready to get an ABORT status,
+ * because I might add a "if regions is big, ask before clobbering" flag.
+ */
+static int
+getregion(struct region *rp)
+{
+       struct line     *flp, *blp;
+       long     fsize, bsize;
+
+       if (curwp->w_markp == NULL) {
+               ewprintf("No mark set in this window");
+               return (FALSE);
+       }
+
+       /* "r_size" always ok */
+       if (curwp->w_dotp == curwp->w_markp) {
+               rp->r_linep = curwp->w_dotp;
+               rp->r_lineno = curwp->w_dotline;
+               if (curwp->w_doto < curwp->w_marko) {
+                       rp->r_offset = curwp->w_doto;
+                       rp->r_size = (RSIZE)(curwp->w_marko - curwp->w_doto);
+               } else {
+                       rp->r_offset = curwp->w_marko;
+                       rp->r_size = (RSIZE)(curwp->w_doto - curwp->w_marko);
+               }
+               return (TRUE);
+       }
+       /* get region size */
+       flp = blp = curwp->w_dotp;
+       bsize = curwp->w_doto;
+       fsize = llength(flp) - curwp->w_doto + 1;
+       while (lforw(flp) != curbp->b_headp || lback(blp) != curbp->b_headp) {
+               if (lforw(flp) != curbp->b_headp) {
+                       flp = lforw(flp);
+                       if (flp == curwp->w_markp) {
+                               rp->r_linep = curwp->w_dotp;
+                               rp->r_offset = curwp->w_doto;
+                               rp->r_lineno = curwp->w_dotline;
+                               return (setsize(rp,
+                                   (RSIZE)(fsize + curwp->w_marko)));
+                       }
+                       fsize += llength(flp) + 1;
+               }
+               if (lback(blp) != curbp->b_headp) {
+                       blp = lback(blp);
+                       bsize += llength(blp) + 1;
+                       if (blp == curwp->w_markp) {
+                               rp->r_linep = blp;
+                               rp->r_offset = curwp->w_marko;
+                               rp->r_lineno = curwp->w_markline;
+                               return (setsize(rp,
+                                   (RSIZE)(bsize - curwp->w_marko)));
+                       }
+               }
+       }
+       ewprintf("Bug: lost mark");
+       return (FALSE);
+}
+
+/*
+ * Set size, and check for overflow.
+ */
+static int
+setsize(struct region *rp, RSIZE size)
+{
+       rp->r_size = size;
+       if (rp->r_size != size) {
+               ewprintf("Region is too large");
+               return (FALSE);
+       }
+       return (TRUE);
+}
+
+#define PREFIXLENGTH 40
+static char    prefix_string[PREFIXLENGTH] = {'>', '\0'};
+
+/*
+ * Prefix the region with whatever is in prefix_string.  Leaves dot at the
+ * beginning of the line after the end of the region.  If an argument is
+ * given, prompts for the line prefix string.
+ */
+/* ARGSUSED */
+int
+prefixregion(int f, int n)
+{
+       struct line     *first, *last;
+       struct region    region;
+       char    *prefix = prefix_string;
+       int      nline;
+       int      s;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read-only");
+               return (FALSE);
+       }
+       if ((f == TRUE) && ((s = setprefix(FFRAND, 1)) != TRUE))
+               return (s);
+
+       /* get # of lines to affect */
+       if ((s = getregion(&region)) != TRUE)
+               return (s);
+       first = region.r_linep;
+       last = (first == curwp->w_dotp) ? curwp->w_markp : curwp->w_dotp;
+       for (nline = 1; first != last; nline++)
+               first = lforw(first);
+
+       /* move to beginning of region */
+       curwp->w_dotp = region.r_linep;
+       curwp->w_doto = region.r_offset;
+       curwp->w_dotline = region.r_lineno;
+
+       /* for each line, go to beginning and insert the prefix string */
+       while (nline--) {
+               (void)gotobol(FFRAND, 1);
+               for (prefix = prefix_string; *prefix; prefix++)
+                       (void)linsert(1, *prefix);
+               (void)forwline(FFRAND, 1);
+       }
+       (void)gotobol(FFRAND, 1);
+       return (TRUE);
+}
+
+/*
+ * Set line prefix string. Used by prefixregion.
+ */
+/* ARGSUSED */
+int
+setprefix(int f, int n)
+{
+       char    buf[PREFIXLENGTH], *rep;
+       int     retval;
+
+       if (prefix_string[0] == '\0')
+               rep = eread("Prefix string: ", buf, sizeof(buf),
+                   EFNEW | EFCR);
+       else
+               rep = eread("Prefix string (default %s): ", buf, sizeof(buf),
+                   EFNUL | EFNEW | EFCR, prefix_string);
+       if (rep == NULL)
+               return (ABORT);
+       if (rep[0] != '\0') {
+               (void)strlcpy(prefix_string, rep, sizeof(prefix_string));
+               retval = TRUE;
+       } else if (rep[0] == '\0' && prefix_string[0] != '\0') {
+               /* CR -- use old one */
+               retval = TRUE;
+       } else
+               retval = FALSE;
+       return (retval);
+}
+
+int
+region_get_data(struct region *reg, char *buf, int len)
+{
+       int      i, off;
+       struct line     *lp;
+
+       off = reg->r_offset;
+       lp = reg->r_linep;
+       for (i = 0; i < len; i++) {
+               if (off == llength(lp)) {
+                       lp = lforw(lp);
+                       if (lp == curbp->b_headp)
+                               break;
+                       off = 0;
+                       buf[i] = '\n';
+               } else {
+                       buf[i] = lgetc(lp, off);
+                       off++;
+               }
+       }
+       buf[i] = '\0';
+       return (i);
+}
+
+void
+region_put_data(const char *buf, int len)
+{
+       int i;
+
+       for (i = 0; buf[i] != '\0' && i < len; i++) {
+               if (buf[i] == '\n')
+                       lnewline();
+               else
+                       linsert(1, buf[i]);
+       }
+}
+
+/*
+ * Mark whole buffer by first traversing to end-of-buffer
+ * and then to beginning-of-buffer. Mark, dot are implicitly
+ * set to eob, bob respectively during traversal.
+ */
+int
+markbuffer(int f, int n)
+{
+       if (gotoeob(f,n) == FALSE)
+               return (FALSE);
+       if (gotobob(f,n) == FALSE)
+               return (FALSE);
+       return (TRUE);
+}
+
+/*
+ * Pipe text from current region to external command.
+ */
+/*ARGSUSED */
+int
+piperegion(int f, int n)
+{
+       char *cmd, cmdbuf[NFILEN];
+
+       /* C-u M-| is not supported yet */
+       if (n > 1)
+               return (ABORT);
+
+       if (curwp->w_markp == NULL) {
+               ewprintf("The mark is not set now, so there is no region");
+               return (FALSE);
+       }
+       if ((cmd = eread("Shell command on region: ", cmdbuf, sizeof(cmdbuf),
+           EFNEW | EFCR)) == NULL || (cmd[0] == '\0'))
+               return (ABORT);
+
+       return (pipeio(cmdbuf));
+}
+
+/*
+ * Create a socketpair, fork and execl cmd passed. STDIN, STDOUT
+ * and STDERR of child process are redirected to socket.
+ */
+int
+pipeio(const char* const cmd)
+{
+       int s[2];
+       char *shellp;
+
+       if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) {
+               ewprintf("socketpair error");
+               return (FALSE);
+       }
+       switch(fork()) {
+       case -1:
+               ewprintf("Can't fork");
+               return (FALSE);
+       case 0:
+               /* Child process */
+               close(s[0]);
+               if (dup2(s[1], STDIN_FILENO) == -1)
+                       _exit(1);
+               if (dup2(s[1], STDOUT_FILENO) == -1)
+                       _exit(1);
+               if (dup2(s[1], STDERR_FILENO) == -1)
+                       _exit(1);
+               if ((shellp = getenv("SHELL")) == NULL)
+                       _exit(1);
+               execl(shellp, "sh", "-c", cmd, (char *)NULL);
+               _exit(1);
+       default:
+               /* Parent process */
+               close(s[1]);
+               return iomux(s[0]);
+       }
+       return (FALSE);
+}
+
+/*
+ * Multiplex read, write on socket fd passed. First get the region,
+ * find/create *Shell Command Output* buffer and clear it's contents.
+ * Poll on the fd for both read and write readiness.
+ */
+int
+iomux(int fd)
+{
+       struct region region;
+       struct buffer *bp;
+       struct pollfd pfd[1];
+       int nfds;
+       char *text, *textcopy;
+       
+       if (getregion(&region) != TRUE)
+               return (FALSE);
+       
+       if ((text = malloc(region.r_size + 1)) == NULL)
+               return (ABORT);
+       
+       region_get_data(&region, text, region.r_size);
+       textcopy = text;
+       fcntl(fd, F_SETFL, O_NONBLOCK);
+       
+       /* There is nothing to write if r_size is zero
+        * but the cmd's output should be read so shutdown 
+        * the socket for writing only.
+        */
+       if (region.r_size == 0)
+               shutdown(fd, SHUT_WR);
+       
+       bp = bfind("*Shell Command Output*", TRUE);
+       bp->b_flag |= BFREADONLY;
+       if (bclear(bp) != TRUE)
+               return (FALSE);
+
+       pfd[0].fd = fd;
+       pfd[0].events = POLLIN | POLLOUT;
+       while ((nfds = poll(pfd, 1, TIMEOUT)) != -1 ||
+           (pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL))) {
+               if (pfd[0].revents & POLLOUT && region.r_size > 0)
+                       pwriteout(fd, &textcopy, &region.r_size);       
+               else if (pfd[0].revents & POLLIN)
+                       if (preadin(fd, bp) == FALSE)
+                               break;
+       }
+       close(fd);
+       free(text);
+       /* In case if last line doesn't have a '\n' add the leftover 
+        * characters to buffer.
+        */
+       if (leftover[0] != '\0') {
+               addline(bp, leftover);
+               leftover[0] = '\0';
+       }
+       if (nfds == 0) {
+               ewprintf("poll timed out");
+               return (FALSE);
+       } else if (nfds == -1) {
+               ewprintf("poll error");
+               return (FALSE);
+       }
+       return (popbuftop(bp, WNONE));
+}
+
+/*
+ * Write some text from region to fd. Once done shutdown the 
+ * write end.
+ */
+void
+pwriteout(int fd, char **text, int *len)
+{
+       int w;
+
+       if (((w = send(fd, *text, *len, MSG_NOSIGNAL)) == -1)) {
+               switch(errno) {
+               case EPIPE:
+                       *len = -1;
+                       break;
+               case EAGAIN:
+                       return;
+               }
+       } else
+               *len -= w;
+
+       *text += w;
+       if (*len <= 0)
+               shutdown(fd, SHUT_WR);          
+}
+
+/*
+ * Read some data from socket fd, break on '\n' and add
+ * to buffer. If couldn't break on newline hold leftover
+ * characters and append in next iteration.
+ */
+int
+preadin(int fd, struct buffer *bp)
+{
+       int len;
+       static int nooutput;
+       char buf[BUFSIZ], *p, *q;
+
+       if ((len = read(fd, buf, BUFSIZ - 1)) == 0) {
+               if (nooutput == 0)
+                       addline(bp, "(Shell command succeeded with no output)");
+               nooutput = 0;
+               return (FALSE);
+       }
+       nooutput = 1;
+       buf[len] = '\0';
+       p = q = buf;
+       if (leftover[0] != '\0' && ((q = strchr(p, '\n')) != NULL)) {
+               *q++ = '\0';
+               if (strlcat(leftover, p, sizeof(leftover)) >=
+                   sizeof(leftover)) {
+                       ewprintf("line too long");
+                       return (FALSE);
+               }
+               addline(bp, leftover);
+               leftover[0] = '\0';
+               p = q;
+       }
+       while ((q = strchr(p, '\n')) != NULL) {
+               *q++ = '\0';
+               addline(bp, p);
+               p = q;
+       }
+       if (strlcpy(leftover, p, sizeof(leftover)) >= sizeof(leftover)) {
+               ewprintf("line too long");
+               return (FALSE);
+       }
+       return (TRUE);
+}
diff --git a/mg/spawn.c b/mg/spawn.c
new file mode 100644 (file)
index 0000000..13fd985
--- /dev/null
@@ -0,0 +1,48 @@
+/*     $OpenBSD: spawn.c,v 1.11 2006/08/01 22:16:03 jason Exp $        */
+
+/* This file is in the public domain. */
+
+/*
+ * Spawn.  Actually just suspends Mg.
+ * Assumes POSIX job control.
+ */
+
+#include "def.h"
+
+#include <termios.h>
+#include <term.h>
+
+/*
+ * This causes mg to send itself a stop signal.  It assumes the parent
+ * shell supports POSIX job control.  If the terminal supports an alternate
+ * screen, we will switch to it.
+ */
+/* ARGSUSED */
+int
+spawncli(int f, int n)
+{
+       sigset_t        oset;
+
+       /* Very similar to what vttidy() does. */
+       ttcolor(CTEXT);
+       ttnowindow();
+       ttmove(nrow - 1, 0);
+       if (epresf != FALSE) {
+               tteeol();
+               epresf = FALSE;
+       }
+       if (ttcooked() == FALSE)
+               return (FALSE);
+
+       /* Exit application mode and tidy. */
+       tttidy();
+       ttflush();
+       (void)sigprocmask(SIG_SETMASK, NULL, &oset);
+       (void)kill(0, SIGTSTP);
+       (void)sigprocmask(SIG_SETMASK, &oset, NULL);
+       ttreinit();
+
+       /* Force repaint. */
+       sgarbf = TRUE;
+       return (ttraw());
+}
diff --git a/mg/sysdef.h b/mg/sysdef.h
new file mode 100644 (file)
index 0000000..6b75614
--- /dev/null
@@ -0,0 +1,30 @@
+/*     $OpenBSD: sysdef.h,v 1.16 2008/09/15 16:11:35 kjell Exp $       */
+
+/* This file is in the public domain. */
+
+/*
+ *             POSIX system header file
+ */
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+
+#define        KBLOCK          8192    /* Kill grow.                    */
+#define        GOOD            0       /* Good exit status.             */
+
+typedef int    RSIZE;          /* Type for file/region sizes    */
+typedef short  KCHAR;          /* Type for internal keystrokes  */
+
+#define MALLOCROUND(m) (m+=7,m&=~7)    /* round up to 8 byte boundary   */
+
+struct fileinfo {
+       uid_t           fi_uid;
+       gid_t           fi_gid;
+       mode_t          fi_mode;
+       struct timespec fi_mtime;       /* Last modified time */
+};
diff --git a/mg/tags.c b/mg/tags.c
new file mode 100644 (file)
index 0000000..92d1425
--- /dev/null
+++ b/mg/tags.c
@@ -0,0 +1,533 @@
+/*     $OpenBSD: tags.c,v 1.5 2012/07/02 08:08:31 lum Exp $    */
+
+/*
+ * This file is in the public domain.
+ *
+ * Author: Sunil Nimmagadda <sunil@sunilnimmagadda.com>
+ */
+
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/tree.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <util.h>
+
+#include "def.h"
+
+struct ctag;
+
+static int               addctag(char *);
+static int               atbow(void);
+void                     closetags(void);
+static int               ctagcmp(struct ctag *, struct ctag *);
+static int               loadbuffer(char *);
+static int               loadtags(const char *);
+static int               pushtag(char *);
+static int               searchpat(char *);
+static struct ctag       *searchtag(char *);
+static char              *strip(char *, size_t);
+static void              unloadtags(void);
+
+#define DEFAULTFN "tags"
+
+char *tagsfn = NULL;
+int  loaded  = FALSE;
+
+/* ctags(1) entries are parsed and maintained in a tree. */
+struct ctag {
+       RB_ENTRY(ctag) entry;
+       char *tag;
+       char *fname;
+       char *pat;
+};
+RB_HEAD(tagtree, ctag) tags = RB_INITIALIZER(&tags);
+RB_GENERATE(tagtree, ctag, entry, ctagcmp);
+
+struct tagpos {
+       SLIST_ENTRY(tagpos) entry;
+       int    doto;
+       int    dotline;
+       char   *bname;
+};
+SLIST_HEAD(tagstack, tagpos) shead = SLIST_HEAD_INITIALIZER(shead);
+
+int
+ctagcmp(struct ctag *s, struct ctag *t)
+{
+       return strcmp(s->tag, t->tag);
+}
+
+/*
+ * Record the filename that contain tags to be used while loading them
+ * on first use. If a filename is already recorded, ask user to retain
+ * already loaded tags (if any) and unload them if user chooses not to.
+ */
+/* ARGSUSED */
+int
+tagsvisit(int f, int n)
+{
+       char fname[NFILEN], *bufp, *temp;
+       struct stat sb;
+       
+       if (getbufcwd(fname, sizeof(fname)) == FALSE)
+               fname[0] = '\0';
+       
+       if (strlcat(fname, DEFAULTFN, sizeof(fname)) >= sizeof(fname)) {
+               ewprintf("Filename too long");
+               return (FALSE);
+       }
+       
+       bufp = eread("visit tags table (default %s): ", fname,
+           NFILEN, EFFILE | EFCR | EFNEW | EFDEF, DEFAULTFN);
+
+       if (stat(bufp, &sb) == -1) {
+               ewprintf("stat: %s", strerror(errno));
+               return (FALSE);
+       } else if (S_ISREG(sb.st_mode) == 0) {
+               ewprintf("Not a regular file");
+               return (FALSE);
+       } else if (access(bufp, R_OK) == -1) {
+               ewprintf("Cannot access file %s", bufp);
+               return (FALSE);
+       }
+       
+       if (tagsfn == NULL) {
+               if (bufp == NULL)
+                       return (ABORT);
+               else if (bufp[0] == '\0') {
+                       if ((tagsfn = strdup(fname)) == NULL) {
+                               ewprintf("Out of memory");
+                               return (FALSE);
+                       }
+               } else {
+                       /* bufp points to local variable, so duplicate. */
+                       if ((tagsfn = strdup(bufp)) == NULL) {
+                               ewprintf("Out of memory");
+                               return (FALSE);
+                       }
+               }
+       } else {
+               if ((temp = strdup(bufp)) == NULL) {
+                       ewprintf("Out of memory");
+                       return (FALSE);
+               }
+               free(tagsfn);
+               tagsfn = temp;
+               if (eyorn("Keep current list of tags table also") == FALSE) {
+                       ewprintf("Starting a new list of tags table");
+                       unloadtags();
+               }
+               loaded = FALSE;
+       }
+       return (TRUE);
+}
+
+/*
+ * Ask user for a tag while treating word at dot as default. Visit tags
+ * file if not yet done, load tags and jump to definition of the tag.
+ */
+int
+findtag(int f, int n)
+{
+       char utok[MAX_TOKEN], dtok[MAX_TOKEN];
+       char *tok, *bufp;
+       int  ret;
+
+       if (curtoken(f, n, dtok) == FALSE)
+               return (FALSE);
+       
+       bufp = eread("Find tag (default %s) ", utok, MAX_TOKEN,
+           EFNUL | EFNEW, dtok);
+
+       if (bufp == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               tok = dtok;
+       else
+               tok = utok;
+       
+       if (tok[0] == '\0') {
+               ewprintf("There is no default tag");
+               return (FALSE);
+       }
+       
+       if (tagsfn == NULL)
+               if ((ret = tagsvisit(f, n)) != TRUE)
+                       return (ret);
+       if (!loaded) {
+               if (loadtags(tagsfn) == FALSE) {
+                       free(tagsfn);
+                       tagsfn = NULL;
+                       return (FALSE);
+               }
+               loaded = TRUE;
+       }
+       return pushtag(tok);
+}
+
+/*
+ * Free tags tree.
+ */
+void
+unloadtags(void)
+{
+       struct ctag *var, *nxt;
+       
+       for (var = RB_MIN(tagtree, &tags); var != NULL; var = nxt) {
+               nxt = RB_NEXT(tagtree, &tags, var);
+               RB_REMOVE(tagtree, &tags, var);
+               /* line parsed with fparseln needs to be freed */
+               free(var->tag);
+               free(var);
+       }
+}
+
+/*
+ * Lookup tag passed in tree and if found, push current location and 
+ * buffername onto stack, load the file with tag definition into a new
+ * buffer and position dot at the pattern.
+ */
+/*ARGSUSED */
+int
+pushtag(char *tok)
+{
+       struct ctag *res;
+       struct tagpos *s;
+       char bname[NFILEN];
+       int doto, dotline;
+       
+       if ((res = searchtag(tok)) == NULL)
+               return (FALSE);
+               
+       doto = curwp->w_doto;
+       dotline = curwp->w_dotline;
+       /* record absolute filenames. Fixes issues when mg's cwd is not the
+        * same as buffer's directory.
+        */
+       if (strlcpy(bname, curbp->b_cwd, sizeof(bname)) >= sizeof(bname)) {
+                   ewprintf("filename too long");
+                   return (FALSE);
+       }
+       if (strlcat(bname, curbp->b_bname, sizeof(bname)) >= sizeof(bname)) {
+                   ewprintf("filename too long");
+                   return (FALSE);
+       }       
+
+       if (loadbuffer(res->fname) == FALSE)
+               return (FALSE);
+       
+       if (searchpat(res->pat) == TRUE) {
+               if ((s = malloc(sizeof(struct tagpos))) == NULL) {
+                       ewprintf("Out of memory");
+                       return (FALSE);
+               }
+               if ((s->bname = strdup(bname)) == NULL) {
+                       ewprintf("Out of memory");
+                       return (FALSE);
+               }
+               s->doto = doto;
+               s->dotline = dotline;
+               SLIST_INSERT_HEAD(&shead, s, entry);
+               return (TRUE);
+       } else {
+               ewprintf("%s: pattern not found", res->tag);
+               return (FALSE);
+       }
+       /* NOTREACHED */
+       return (FALSE);
+}
+
+/*
+ * If tag stack is not empty pop stack and jump to recorded buffer, dot.
+ */
+/* ARGSUSED */
+int
+poptag(int f, int n)
+{
+       struct line *dotp;
+       struct tagpos *s;
+       
+       if (SLIST_EMPTY(&shead)) {
+               ewprintf("No previous location for find-tag invocation");
+               return (FALSE);
+       }
+       s = SLIST_FIRST(&shead);
+       SLIST_REMOVE_HEAD(&shead, entry);
+       if (loadbuffer(s->bname) == FALSE)
+               return (FALSE);
+       curwp->w_dotline = s->dotline;
+       curwp->w_doto = s->doto;
+       
+       /* storing of dotp in tagpos wouldn't work out in cases when
+        * that buffer is killed by user(dangling pointer). Explicitly
+        * traverse till dotline for correct handling. 
+        */
+       dotp = curwp->w_bufp->b_headp;
+       while (s->dotline--)
+               dotp = dotp->l_fp;
+       
+       curwp->w_dotp = dotp;
+       free(s->bname);
+       free(s);
+       return (TRUE);
+}
+
+/*
+ * Parse the tags file and construct the tags tree. Remove escape 
+ * characters while parsing the file.
+ */
+int
+loadtags(const char *fn)
+{
+       char *l;
+       FILE *fd;
+       
+       if ((fd = fopen(fn, "r")) == NULL) {
+               ewprintf("Unable to open tags file: %s", fn);
+               return (FALSE);
+       }
+       while ((l = fparseln(fd, NULL, NULL, "\\\\\0",
+           FPARSELN_UNESCCONT | FPARSELN_UNESCREST)) != NULL) {
+               if (addctag(l) == FALSE) {
+                       fclose(fd);
+                       return (FALSE);
+               }
+       }
+       fclose(fd);
+       return (TRUE);
+}
+
+/*
+ * Cleanup and destroy tree and stack.
+ */
+void
+closetags(void)
+{
+       struct tagpos *s;       
+       
+       while (!SLIST_EMPTY(&shead)) {
+               s = SLIST_FIRST(&shead);
+               SLIST_REMOVE_HEAD(&shead, entry);
+               free(s->bname);
+               free(s);
+       }
+       unloadtags();
+       free(tagsfn);
+}
+
+/*
+ * Strip away any special characters in pattern.
+ * The pattern in ctags isn't a true regular expression. Its of the form
+ * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip 
+ * the leading and trailing special characters so the pattern matching
+ * would be a simple string compare. Escape character is taken care by 
+ * fparseln.
+ */
+char *
+strip(char *s, size_t len)
+{
+       /* first strip trailing special chars */        
+       s[len - 1] = '\0';
+       if (s[len - 2] == '$')
+               s[len - 2] = '\0';
+       
+       /* then strip leading special chars */
+       s++;
+       if (*s == '^')
+               s++;
+       
+       return s;
+}
+
+/*
+ * tags line is of the format "<tag>\t<filename>\t<pattern>". Split them
+ * by replacing '\t' with '\0'. This wouldn't alter the size of malloc'ed
+ * l, and can be freed during cleanup.
+ */
+int
+addctag(char *l)
+{
+       struct ctag *t;
+       
+       if ((t = malloc(sizeof(struct ctag))) == NULL) {
+               ewprintf("Out of memory");
+               return (FALSE);
+       }
+       t->tag = l;
+       if ((l = strchr(l, '\t')) == NULL)
+               goto cleanup;
+       *l++ = '\0';
+       t->fname = l;
+       if ((l = strchr(l, '\t')) == NULL)
+               goto cleanup;
+       *l++ = '\0';
+       if (*l == '\0')
+               goto cleanup;
+       t->pat = strip(l, strlen(l));
+       RB_INSERT(tagtree, &tags, t);
+       return (TRUE);
+cleanup:
+       free(t);
+       free(l);
+       return (TRUE);
+}
+
+/*
+ * Search through each line of buffer for pattern.
+ */
+int
+searchpat(char *pat)
+{
+       struct line *lp;
+       int dotline;
+       size_t plen;
+
+       plen = strlen(pat);
+       dotline = 1;
+       lp = lforw(curbp->b_headp);
+       while (lp != curbp->b_headp) {
+               if (ltext(lp) != NULL && plen <= llength(lp) &&
+                   (strncmp(pat, ltext(lp), plen) == 0)) {
+                       curwp->w_doto = 0;
+                       curwp->w_dotp = lp;
+                       curwp->w_dotline = dotline;
+                       return (TRUE);
+               } else {
+                       lp = lforw(lp);
+                       dotline++;
+               }
+       }
+       return (FALSE);
+}
+
+/*
+ * Return TRUE if dot is at beginning of a word or at beginning 
+ * of line, else FALSE.
+ */
+int
+atbow(void)
+{
+       if (curwp->w_doto == 0)
+               return (TRUE);
+       if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) &&
+           !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1]))
+               return (TRUE);
+       return (FALSE);
+}
+
+/*
+ * Extract the word at dot without changing dot position.
+ */
+int
+curtoken(int f, int n, char *token)
+{
+       struct line *odotp;
+       int odoto, tdoto, odotline, size, r;
+       char c;
+       
+       /* Underscore character is to be treated as "inword" while
+        * processing tokens unlike mg's default word traversal. Save
+        * and restore it's cinfo value so that tag matching works for
+        * identifier with underscore.
+        */
+       c = cinfo['_'];
+       cinfo['_'] = _MG_W;
+       
+       odotp = curwp->w_dotp;
+       odoto = curwp->w_doto;
+       odotline = curwp->w_dotline;
+       
+       /* Move backword unless we are at the beginning of a word or at
+        * beginning of line.
+        */
+       if (!atbow())
+               if ((r = backword(f, n)) == FALSE)
+                       goto cleanup;
+               
+       tdoto = curwp->w_doto;
+
+       if ((r = forwword(f, n)) == FALSE)
+               goto cleanup;
+       
+       /* strip away leading whitespace if any like emacs. */
+       while (ltext(curwp->w_dotp) && 
+           isspace(curwp->w_dotp->l_text[tdoto]))
+               tdoto++;
+
+       size = curwp->w_doto - tdoto;
+       if (size <= 0 || size >= MAX_TOKEN || 
+           ltext(curwp->w_dotp) == NULL) {
+               r = FALSE;
+               goto cleanup;
+       }    
+       strncpy(token, ltext(curwp->w_dotp) + tdoto, size);
+       token[size] = '\0';
+       r = TRUE;
+       
+cleanup:
+       cinfo['_'] = c;
+       curwp->w_dotp = odotp;
+       curwp->w_doto = odoto;
+       curwp->w_dotline = odotline;
+       return (r);
+}
+
+/*
+ * Search tagstree for a given token.
+ */
+struct ctag *
+searchtag(char *tok)
+{
+       struct ctag t, *res;
+
+       t.tag = tok;
+       if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) {
+               ewprintf("No tag containing %s", tok);
+               return (NULL);
+       }
+       return res;
+}
+
+/*
+ * This is equivalent to filevisit from file.c.
+ * Look around to see if we can find the file in another buffer; if we
+ * can't find it, create a new buffer, read in the text, and switch to
+ * the new buffer. *scratch*, *grep*, *compile* needs to be handled 
+ * differently from other buffers which have "filenames".
+ */
+int
+loadbuffer(char *bname)
+{
+       struct buffer *bufp;
+       char *adjf;
+
+       /* check for special buffers which begin with '*' */
+       if (bname[0] == '*') {
+               if ((bufp = bfind(bname, FALSE)) != NULL) {
+                       curbp = bufp;
+                       return (showbuffer(bufp, curwp, WFFULL));
+               } else {
+                       return (FALSE);
+               }
+       } else {        
+               if ((adjf = adjustname(bname, TRUE)) == NULL)
+                       return (FALSE);
+               if ((bufp = findbuffer(adjf)) == NULL)
+                       return (FALSE);
+       }
+       curbp = bufp;
+       if (showbuffer(bufp, curwp, WFFULL) != TRUE)
+               return (FALSE);
+       if (bufp->b_fname[0] == '\0') {
+               if (readin(adjf) != TRUE) {
+                       killbuffer(bufp);
+                       return (FALSE);
+               }
+       }
+       return (TRUE);
+}
diff --git a/mg/theo.c b/mg/theo.c
new file mode 100644 (file)
index 0000000..408f9e1
--- /dev/null
+++ b/mg/theo.c
@@ -0,0 +1,192 @@
+/*     $OpenBSD: theo.c,v 1.124 2012/07/09 22:24:36 mlarkin Exp $      */
+/*
+ * Copyright (c) 2002 Artur Grabowski <art@openbsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "def.h"
+#include "kbd.h"
+#include "funmap.h"
+
+void           theo_init(void);
+static int     theo_analyze(int, int);
+static int     theo(int, int);
+
+static PF theo_pf[] = {
+       theo_analyze
+};
+
+static struct KEYMAPE (1 + IMAPEXT) theomap = {
+       1,
+       1 + IMAPEXT,
+       rescan,
+       {
+               { CCHR('M'), CCHR('M'), theo_pf, NULL }
+       }
+};
+
+void
+theo_init(void)
+{
+       funmap_add(theo, "theo");
+       maps_add((KEYMAP *)&theomap, "theo");
+}
+
+/* ARGSUSED */
+static int
+theo(int f, int n)
+{
+       struct buffer   *bp;
+       struct mgwin    *wp;
+
+       bp = bfind("theo", TRUE);
+       if (bclear(bp) != TRUE)
+               return (FALSE);
+
+       bp->b_modes[0] = name_mode("fundamental");
+       bp->b_modes[1] = name_mode("theo");
+       bp->b_nmodes = 1;
+
+       if ((wp = popbuf(bp, WNONE)) == NULL)
+               return (FALSE);
+
+       curbp = bp;
+       curwp = wp;
+       onlywind(f, n);
+
+       return (TRUE);
+}
+
+static const char *talk[] = {
+       "Write more code.",
+       "Make more commits.",
+       "That's because you have been slacking.",
+       "slacker!",
+       "That's what happens when you're lazy.",
+       "idler!",
+       "slackass!",
+       "lazy bum!",
+       "Stop slacking you lazy bum!",
+       "slacker slacker lazy bum bum bum slacker!",
+       "I could search... but I'm a lazy bum ;)",
+       "sshutup sshithead, ssharpsshooting susshi sshplats ssharking assholes.",
+       "Lazy bums slacking on your asses.",
+       "35 commits an hour? That's pathetic!",
+       "Fine software takes time to prepare.  Give a little slack.",
+       "I am just stating a fact",
+       "you bring new meaning to the terms slackass. I will have to invent a new term.",
+       "if they cut you out, muddy their back yards",
+       "Make them want to start over, and play nice the next time.",
+       "It is clear that this has not been thought through.",
+       "avoid using abort().  it is not nice.",
+       "That's the most ridiculous thing I've heard in the last two or three minutes!",
+       "I'm not just doing this for crowd response. I need to be right.",
+       "I'd put a fan on my bomb.. And blinking lights...",
+       "I love to fight",
+       "No sane people allowed here.  Go home.",
+       "you have to stop peeing on your breakfast",
+       "feature requests come from idiots",
+       "henning and darren / sitting in a tree / t o k i n g / a joint or three",
+       "KICK ASS. TIME FOR A JASON LOVE IN!  WE CAN ALL GET LOST IN HIS HAIR!",
+       "shame on you for following my rules.",
+       "altq's parser sucks dead whale farts through the finest chemistry pipette's",
+       "screw this operating system shit, i just want to drive!",
+       "Search for fuck.  Anytime you see that word, you have a paragraph to write.",
+       "Yes, but the ports people are into S&M.",
+       "Buttons are for idiots.",
+       "We are not hackers. We are turd polishing craftsmen.",
+       "who cares.  style(9) can bite my ass",
+       "It'd be one fucking happy planet if it wasn't for what's under this fucking sticker.",
+       "I would explain, but I am too drunk.",
+       "you slackers don't deserve pictures yet",
+       "Vegetarian my ass",
+       "Wait a minute, that's a McNally's!",
+       "don't they recognize their moral responsibility to entertain me?",
+       "#ifdef is for emacs developers.",
+       "Many well known people become net-kooks in their later life, because they lose touch with reality.",
+       "You're not allowed to have an opinion.",
+       "tweep tweep tweep",
+       "Quite frankly, SSE's alignment requirement is the most utterly retarded idea since eating your own shit.",
+       "Holy verbose prom startup Batman.",
+       "Any day now, when we sell out.",
+       "optimism in man kind does not belong here",
+       "First user who tries to push this button, he pounds into the ground with a rant of death.",
+       "we did farts.  now we do sperm.  we are cutting edge.",
+       "the default configuration is a mixture of piss, puke, shit, and bloody entrails.",
+       "Stop wasting your time reading people's licenses.",
+       "doing it with environment variables is OH SO SYSTEM FIVE LIKE OH MY GOD PASS ME THE SPOON",
+       "Linux is fucking POO, not just bad, bad REALLY REALLY BAD",
+       "penguins are not much more than chickens that swim.",
+       "i am a packet sniffing fool, let me wipe my face with my own poo",
+       "Whiners.  They scale really well.",
+       "in your world, you would have a checklist of 50 fucking workarounds just to make a coffee.",
+       "for once, I have nothing to say.",
+       "You have no idea how fucked we are",
+       "You can call it fart if you want to.",
+       "wavelan is a battle field",
+       "You are in a maze of gpio pins, all alike, all undocumented, and a few are wired to bombs.",
+       "And that is why humppa sucks... cause it has no cause.",
+       "cache aliasing is a problem that would have stopped in 1992 if someone had killed about 5 people who worked at Sun.",
+       "Don't spread rumours about me being gentle.",
+       "If municipal water filtering equipment was built by the gcc developers, the western world would be dead by now.",
+       "kettenis supported a new machine in my basement and all I got to do was fix a 1 character typo in his html page commit.",
+       "industry told us a lesson: when you're an asshole, they mail you hardware",
+       "I was joking, really.  I think I am funny :-)",
+       "the kernel is a harsh mistress",
+       "Have I ever been subtle? If my approach ever becomes subtle, shoot me.",
+       "the acpi stabs you in the back.  the acpi stabs you in the back. you die ...",
+       "My cats are more observant than you.",
+       "our kernels have no bugs",
+       "style(9) has all these fascist rules, and i have a problem with some of them because i didn't come up with them",
+       "I'm not very reliable",
+       "I don't like control",
+       "You aren't being conservative -- you are trying to be a caveman.",
+       "nfs loves everyone",
+       "basically, dung beetles fucking.  that's what kerberosV + openssl is like",
+       "I would rather run Windows than use vi.",
+       "if you assign that responsibility to non-hikers I will walk over and cripple you now.",
+       "i ojbect two yoru splelng of achlhlocis.",
+       "We have two kinds of developers - those that deal with their own shit and those that deal with other people's shit."
+};
+
+static const int ntalk = sizeof(talk)/sizeof(talk[0]);
+
+/* ARGSUSED */
+static int
+theo_analyze(int f, int n)
+{
+       const char      *str;
+       int              len;
+
+       str = talk[arc4random_uniform(ntalk)];
+       len = strlen(str);
+
+       newline(FFRAND, 2);
+
+       while (len--)
+               linsert(1, *str++);
+
+       newline(FFRAND, 2);
+
+       return (TRUE);
+}
diff --git a/mg/tty.c b/mg/tty.c
new file mode 100644 (file)
index 0000000..f803cc7
--- /dev/null
+++ b/mg/tty.c
@@ -0,0 +1,447 @@
+/*     $OpenBSD: tty.c,v 1.30 2008/09/15 16:11:35 kjell Exp $  */
+
+/* This file is in the public domain. */
+
+/*
+ * Terminfo display driver
+ *
+ * Terminfo is a terminal information database and routines to describe
+ * terminals on most modern UNIX systems.  Many other systems have adopted
+ * this as a reasonable way to allow for widely varying and ever changing
+ * varieties of terminal types.         This should be used where practical.
+ */
+/*
+ * Known problems: If you have a terminal with no clear to end of screen and
+ * memory of lines below the ones visible on the screen, display will be
+ * wrong in some cases.  I doubt that any such terminal was ever made, but I
+ * thought everyone with delete line would have clear to end of screen too...
+ *
+ * Code for terminals without clear to end of screen and/or clear to end of line
+ * has not been extensively tested.
+ *
+ * Cost calculations are very rough.  Costs of insert/delete line may be far
+ * from the truth.  This is accentuated by display.c not knowing about
+ * multi-line insert/delete.
+ *
+ * Using scrolling region vs insert/delete line should probably be based on cost
+ * rather than the assumption that scrolling region operations look better.
+ */
+
+#include "def.h"
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+
+#include <term.h>
+
+static int      charcost(char *);
+
+static int      cci;
+static int      insdel;        /* Do we have both insert & delete line? */
+static char    *scroll_fwd;    /* How to scroll forward. */
+
+static void     winchhandler(int);
+
+/* ARGSUSED */
+static void
+winchhandler(int sig)
+{
+       winch_flag = 1;
+}
+
+/*
+ * Initialize the terminal when the editor
+ * gets started up.
+ */
+void
+ttinit(void)
+{
+       int errret;
+
+       if (setupterm(NULL, 1, &errret))
+               panic("Terminal setup failed");
+
+       signal(SIGWINCH, winchhandler);
+       signal(SIGCONT, winchhandler);
+       siginterrupt(SIGWINCH, 1);
+
+       scroll_fwd = scroll_forward;
+       if (scroll_fwd == NULL || *scroll_fwd == '\0') {
+               /* this is what GNU Emacs does */
+               scroll_fwd = parm_down_cursor;
+               if (scroll_fwd == NULL || *scroll_fwd == '\0')
+                       scroll_fwd = "\n";
+       }
+
+       if (cursor_address == NULL || cursor_up == NULL)
+               panic("This terminal is too stupid to run mg");
+
+       /* set nrow & ncol */
+       ttresize();
+
+       if (!clr_eol)
+               tceeol = ncol;
+       else
+               tceeol = charcost(clr_eol);
+
+       /* Estimate cost of inserting a line */
+       if (change_scroll_region && scroll_reverse)
+               tcinsl = charcost(change_scroll_region) * 2 +
+                   charcost(scroll_reverse);
+       else if (parm_insert_line)
+               tcinsl = charcost(parm_insert_line);
+       else if (insert_line)
+               tcinsl = charcost(insert_line);
+       else
+               /* make this cost high enough */
+               tcinsl = nrow * ncol;
+
+       /* Estimate cost of deleting a line */
+       if (change_scroll_region)
+               tcdell = charcost(change_scroll_region) * 2 +
+                   charcost(scroll_fwd);
+       else if (parm_delete_line)
+               tcdell = charcost(parm_delete_line);
+       else if (delete_line)
+               tcdell = charcost(delete_line);
+       else
+               /* make this cost high enough */
+               tcdell = nrow * ncol;
+
+       /* Flag to indicate that we can both insert and delete lines */
+       insdel = (insert_line || parm_insert_line) &&
+           (delete_line || parm_delete_line);
+
+       if (enter_ca_mode)
+               /* enter application mode */
+               putpad(enter_ca_mode, 1);
+
+       ttresize();
+}
+
+/*
+ * Re-initialize the terminal when the editor is resumed.
+ * The keypad_xmit doesn't really belong here but...
+ */
+void
+ttreinit(void)
+{
+       /* check if file was modified while we were gone */
+       if (fchecktime(curbp) != TRUE) {
+               curbp->b_flag |= BFDIRTY;
+       }
+
+       if (enter_ca_mode)
+               /* enter application mode */
+               putpad(enter_ca_mode, 1);
+
+       if (keypad_xmit)
+               /* turn on keypad */
+               putpad(keypad_xmit, 1);
+
+       ttresize();
+}
+
+/*
+ * Clean up the terminal, in anticipation of a return to the command
+ * interpreter. This is a no-op on the ANSI display. On the SCALD display,
+ * it sets the window back to half screen scrolling. Perhaps it should
+ * query the display for the increment, and put it back to what it was.
+ */
+void
+tttidy(void)
+{
+#ifdef XKEYS
+       ttykeymaptidy();
+#endif /* XKEYS */
+
+       /* set the term back to normal mode */
+       if (exit_ca_mode)
+               putpad(exit_ca_mode, 1);
+}
+
+/*
+ * Move the cursor to the specified origin 0 row and column position. Try to
+ * optimize out extra moves; redisplay may have left the cursor in the right
+ * location last time!
+ */
+void
+ttmove(int row, int col)
+{
+       if (ttrow != row || ttcol != col) {
+               putpad(tgoto(cursor_address, col, row), 1);
+               ttrow = row;
+               ttcol = col;
+       }
+}
+
+/*
+ * Erase to end of line.
+ */
+void
+tteeol(void)
+{
+       int     i;
+
+       if (clr_eol)
+               putpad(clr_eol, 1);
+       else {
+               i = ncol - ttcol;
+               while (i--)
+                       ttputc(' ');
+               ttrow = ttcol = HUGE;
+       }
+}
+
+/*
+ * Erase to end of page.
+ */
+void
+tteeop(void)
+{
+       int     line;
+
+       if (clr_eos)
+               putpad(clr_eos, nrow - ttrow);
+       else {
+               putpad(clr_eol, 1);
+               if (insdel)
+                       ttdell(ttrow + 1, lines, lines - ttrow - 1);
+               else {
+                       /* do it by hand */
+                       for (line = ttrow + 1; line <= lines; ++line) {
+                               ttmove(line, 0);
+                               tteeol();
+                       }
+               }
+               ttrow = ttcol = HUGE;
+       }
+}
+
+/*
+ * Make a noise.
+ */
+void
+ttbeep(void)
+{
+       putpad(bell, 1);
+       ttflush();
+}
+
+/*
+ * Insert nchunk blank line(s) onto the screen, scrolling the last line on
+ * the screen off the bottom.  Use the scrolling region if possible for a
+ * smoother display.  If there is no scrolling region, use a set of insert
+ * and delete line sequences.
+ */
+void
+ttinsl(int row, int bot, int nchunk)
+{
+       int     i, nl;
+
+       /* Case of one line insert is special. */
+       if (row == bot) {
+               ttmove(row, 0);
+               tteeol();
+               return;
+       }
+       if (change_scroll_region && scroll_reverse) {
+               /* Use scroll region and back index      */
+               nl = bot - row;
+               ttwindow(row, bot);
+               ttmove(row, 0);
+               while (nchunk--)
+                       putpad(scroll_reverse, nl);
+               ttnowindow();
+               return;
+       } else if (insdel) {
+               ttmove(1 + bot - nchunk, 0);
+               nl = nrow - ttrow;
+               if (parm_delete_line)
+                       putpad(tgoto(parm_delete_line, 0, nchunk), nl);
+               else
+                       /* For all lines in the chunk... */
+                       for (i = 0; i < nchunk; i++)
+                               putpad(delete_line, nl);
+               ttmove(row, 0);
+
+               /* ttmove() changes ttrow */
+               nl = nrow - ttrow;
+
+               if (parm_insert_line)
+                       putpad(tgoto(parm_insert_line, 0, nchunk), nl);
+               else
+                       /* For all lines in the chunk */
+                       for (i = 0; i < nchunk; i++)
+                               putpad(insert_line, nl);
+               ttrow = HUGE;
+               ttcol = HUGE;
+       } else
+               panic("ttinsl: Can't insert/delete line");
+}
+
+/*
+ * Delete nchunk line(s) from "row", replacing the bottom line on the
+ * screen with a blank line.  Unless we're using the scrolling region,
+ * this is done with crafty sequences of insert and delete lines.  The
+ * presence of the echo area makes a boundary condition go away.
+ */
+void
+ttdell(int row, int bot, int nchunk)
+{
+       int     i, nl;
+
+       /* One line special cases */
+       if (row == bot) {
+               ttmove(row, 0);
+               tteeol();
+               return;
+       }
+       /* scrolling region */
+       if (change_scroll_region) {
+               nl = bot - row;
+               ttwindow(row, bot);
+               ttmove(bot, 0);
+               while (nchunk--)
+                       putpad(scroll_fwd, nl);
+               ttnowindow();
+       /* else use insert/delete line */
+       } else if (insdel) {
+               ttmove(row, 0);
+               nl = nrow - ttrow;
+               if (parm_delete_line)
+                       putpad(tgoto(parm_delete_line, 0, nchunk), nl);
+               else
+                       /* For all lines in the chunk    */
+                       for (i = 0; i < nchunk; i++)
+                               putpad(delete_line, nl);
+               ttmove(1 + bot - nchunk, 0);
+
+               /* ttmove() changes ttrow */
+               nl = nrow - ttrow;
+               if (parm_insert_line)
+                       putpad(tgoto(parm_insert_line, 0, nchunk), nl);
+               else
+                       /* For all lines in the chunk */
+                       for (i = 0; i < nchunk; i++)
+                               putpad(insert_line, nl);
+               ttrow = HUGE;
+               ttcol = HUGE;
+       } else
+               panic("ttdell: Can't insert/delete line");
+}
+
+/*
+ * This routine sets the scrolling window on the display to go from line
+ * "top" to line "bot" (origin 0, inclusive).  The caller checks for the
+ * pathological 1-line scroll window which doesn't work right and avoids
+ * it.  The "ttrow" and "ttcol" variables are set to a crazy value to
+ * ensure that the next call to "ttmove" does not turn into a no-op (the
+ * window adjustment moves the cursor).
+ */
+void
+ttwindow(int top, int bot)
+{
+       if (change_scroll_region && (tttop != top || ttbot != bot)) {
+               putpad(tgoto(change_scroll_region, bot, top), nrow - ttrow);
+               ttrow = HUGE;   /* Unknown.              */
+               ttcol = HUGE;
+               tttop = top;    /* Remember region.      */
+               ttbot = bot;
+       }
+}
+
+/*
+ * Switch to full screen scroll. This is used by "spawn.c" just before it
+ * suspends the editor and by "display.c" when it is getting ready to
+ * exit.  This function does a full screen scroll by telling the terminal
+ * to set a scrolling region that is lines or nrow rows high, whichever is
+ * larger.  This behavior seems to work right on systems where you can set
+ * your terminal size.
+ */
+void
+ttnowindow(void)
+{
+       if (change_scroll_region) {
+               putpad(tgoto(change_scroll_region,
+                   (nrow > lines ? nrow : lines) - 1, 0), nrow - ttrow);
+               ttrow = HUGE;   /* Unknown.              */
+               ttcol = HUGE;
+               tttop = HUGE;   /* No scroll region.     */
+               ttbot = HUGE;
+       }
+}
+
+/*
+ * Set the current writing color to the specified color. Watch for color
+ * changes that are not going to do anything (the color is already right)
+ * and don't send anything to the display.  The rainbow version does this
+ * in putline.s on a line by line basis, so don't bother sending out the
+ * color shift.
+ */
+void
+ttcolor(int color)
+{
+       if (color != tthue) {
+               if (color == CTEXT)
+                       /* normal video */
+                       putpad(exit_standout_mode, 1);
+               else if (color == CMODE)
+                       /* reverse video */
+                       putpad(enter_standout_mode, 1);
+               /* save the color */
+               tthue = color;
+       }
+}
+
+/*
+ * This routine is called by the "refresh the screen" command to try
+ * to resize the display. Look in "window.c" to see how
+ * the caller deals with a change.
+ *
+ * We use `newrow' and `newcol' so vtresize() know the difference between the
+ * new and old settings.
+ */
+void
+ttresize(void)
+{
+       int newrow = 0, newcol = 0;
+
+#ifdef TIOCGWINSZ
+       struct  winsize winsize;
+
+       if (ioctl(0, TIOCGWINSZ, &winsize) == 0) {
+               newrow = winsize.ws_row;
+               newcol = winsize.ws_col;
+       }
+#endif
+       if ((newrow <= 0 || newcol <= 0) &&
+           ((newrow = lines) <= 0 || (newcol = columns) <= 0)) {
+               newrow = 24;
+               newcol = 80;
+       }
+       if (vtresize(1, newrow, newcol) != TRUE)
+               panic("vtresize failed");
+}
+
+/*
+ * fake char output for charcost()
+ */
+/* ARGSUSED */
+static int
+fakec(int c)
+{
+       cci++;
+       return (0);
+}
+
+/* calculate the cost of doing string s */
+static int
+charcost(char *s)
+{
+       cci = 0;
+
+       tputs(s, nrow, fakec);
+       return (cci);
+}
diff --git a/mg/ttydef.h b/mg/ttydef.h
new file mode 100644 (file)
index 0000000..b32b1c0
--- /dev/null
@@ -0,0 +1,25 @@
+/*     $OpenBSD: ttydef.h,v 1.10 2005/11/20 03:53:45 deraadt Exp $     */
+
+/* This file is in the public domain. */
+
+#ifndef TTYDEF_H
+#define TTYDEF_H
+
+/*
+ *     Terminfo terminal file, nothing special, just make it big
+ *     enough for windowing systems.
+ */
+
+#define STANDOUT_GLITCH                        /* possible standout glitch      */
+#define TERMCAP                                /* for possible use in ttyio.c   */
+
+#ifdef undef
+#define MOVE_STANDOUT                  /* don't move in standout mode   */
+#endif /* undef */
+
+#define        putpad(str, num)        tputs(str, num, ttputc)
+
+#define        KFIRST  K00
+#define        KLAST   K00
+
+#endif /* TTYDEF_H */
diff --git a/mg/ttyio.c b/mg/ttyio.c
new file mode 100644 (file)
index 0000000..228a370
--- /dev/null
@@ -0,0 +1,225 @@
+/*     $OpenBSD: ttyio.c,v 1.32 2008/02/05 12:53:38 reyk Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ * POSIX terminal I/O.
+ *
+ * The functions in this file negotiate with the operating system for
+ * keyboard characters, and write characters to the display in a barely
+ * buffered fashion.
+ */
+#include "def.h"
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <term.h>
+
+#define NOBUF  512                     /* Output buffer size. */
+
+#ifndef TCSASOFT
+#define TCSASOFT       0
+#endif
+
+int    ttstarted;
+char   obuf[NOBUF];                    /* Output buffer. */
+size_t nobuf;                          /* Buffer count. */
+struct termios oldtty;                 /* POSIX tty settings. */
+struct termios newtty;
+int    nrow;                           /* Terminal size, rows. */
+int    ncol;                           /* Terminal size, columns. */
+
+/*
+ * This function gets called once, to set up the terminal.
+ * On systems w/o TCSASOFT we turn off off flow control,
+ * which isn't really the right thing to do.
+ */
+void
+ttopen(void)
+{
+       if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO))
+               panic("standard input and output must be a terminal");
+
+       if (ttraw() == FALSE)
+               panic("aborting due to terminal initialize failure");
+}
+
+/*
+ * This function sets the terminal to RAW mode, as defined for the current
+ * shell.  This is called both by ttopen() above and by spawncli() to
+ * get the current terminal settings and then change them to what
+ * mg expects. Thus, tty changes done while spawncli() is in effect
+ * will be reflected in mg.
+ */
+int
+ttraw(void)
+{
+       if (tcgetattr(0, &oldtty) < 0) {
+               ewprintf("ttopen can't get terminal attributes");
+               return (FALSE);
+       }
+       (void)memcpy(&newtty, &oldtty, sizeof(newtty));
+       /* Set terminal to 'raw' mode and ignore a 'break' */
+       newtty.c_cc[VMIN] = 1;
+       newtty.c_cc[VTIME] = 0;
+       newtty.c_iflag |= IGNBRK;
+       newtty.c_iflag &= ~(BRKINT | PARMRK | INLCR | IGNCR | ICRNL | IXON);
+       newtty.c_oflag &= ~OPOST;
+       newtty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+
+#if !TCSASOFT
+       /*
+        * If we don't have TCSASOFT, force terminal to
+        * 8 bits, no parity.
+        */
+       newtty.c_iflag &= ~ISTRIP;
+       newtty.c_cflag &= ~(CSIZE | PARENB);
+       newtty.c_cflag |= CS8;
+#endif
+       if (tcsetattr(0, TCSASOFT | TCSADRAIN, &newtty) < 0) {
+               ewprintf("ttopen can't tcsetattr");
+               return (FALSE);
+       }
+       ttstarted = 1;
+
+       return (TRUE);
+}
+
+/*
+ * This function gets called just before we go back home to the shell.
+ * Put all of the terminal parameters back.
+ * Under UN*X this just calls ttcooked(), but the ttclose() hook is in
+ * because vttidy() in display.c expects it for portability reasons.
+ */
+void
+ttclose(void)
+{
+       if (ttstarted) {
+               if (ttcooked() == FALSE)
+                       panic("");      /* ttcooked() already printf'd */
+               ttstarted = 0;
+       }
+}
+
+/*
+ * This function restores all terminal settings to their default values,
+ * in anticipation of exiting or suspending the editor.
+ */
+int
+ttcooked(void)
+{
+       ttflush();
+       if (tcsetattr(0, TCSASOFT | TCSADRAIN, &oldtty) < 0) {
+               ewprintf("ttclose can't tcsetattr");
+               return (FALSE);
+       }
+       return (TRUE);
+}
+
+/*
+ * Write character to the display.  Characters are buffered up,
+ * to make things a little bit more efficient.
+ */
+int
+ttputc(int c)
+{
+       if (nobuf >= NOBUF)
+               ttflush();
+       obuf[nobuf++] = c;
+       return (c);
+}
+
+/*
+ * Flush output.
+ */
+void
+ttflush(void)
+{
+       ssize_t  written;
+       char    *buf = obuf;
+
+       if (nobuf == 0)
+               return;
+
+       while ((written = write(fileno(stdout), buf, nobuf)) != nobuf) {
+               if (written == -1) {
+                       if (errno == EINTR)
+                               continue;
+                       panic("ttflush write failed");
+               }
+               buf += written;
+               nobuf -= written;
+       }
+       nobuf = 0;
+}
+
+/*
+ * Read character from terminal. All 8 bits are returned, so that you
+ * can use a multi-national terminal.
+ */
+int
+ttgetc(void)
+{
+       char    c;
+       ssize_t ret;
+
+       do {
+               ret = read(STDIN_FILENO, &c, 1);
+               if (ret == -1 && errno == EINTR) {
+                       if (winch_flag) {
+                               redraw(0, 0);
+                               winch_flag = 0;
+                       }
+               } else if (ret == 1)
+                       break;
+       } while (1);
+       return ((int) c) & 0xFF;
+}
+
+/*
+ * Returns TRUE if there are characters waiting to be read.
+ */
+int
+charswaiting(void)
+{
+       int     x;
+
+       return ((ioctl(0, FIONREAD, &x) < 0) ? 0 : x);
+}
+
+/*
+ * panic - just exit, as quickly as we can.
+ */
+void
+panic(char *s)
+{
+       ttclose();
+       (void) fputs("panic: ", stderr);
+       (void) fputs(s, stderr);
+       (void) fputc('\n', stderr);
+       exit(1);
+}
+
+/*
+ * This function returns FALSE if any characters have showed up on the
+ * tty before 'msec' milliseconds.
+ */
+int
+ttwait(int msec)
+{
+       fd_set          readfds;
+       struct timeval  tmout;
+
+       FD_ZERO(&readfds);
+       FD_SET(0, &readfds);
+
+       tmout.tv_sec = msec/1000;
+       tmout.tv_usec = msec - tmout.tv_sec * 1000;
+
+       if ((select(1, &readfds, NULL, NULL, &tmout)) == 0)
+               return (TRUE);
+       return (FALSE);
+}
diff --git a/mg/ttykbd.c b/mg/ttykbd.c
new file mode 100644 (file)
index 0000000..a160a3e
--- /dev/null
@@ -0,0 +1,89 @@
+/*     $OpenBSD: ttykbd.c,v 1.16 2012/04/12 04:47:59 lum Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ * Name:       MG 2a
+ *             Terminfo keyboard driver using key files
+ * Created:    22-Nov-1987 Mic Kaczmarczik (mic@emx.cc.utexas.edu)
+ */
+
+#include "def.h"
+#include "kbd.h"
+
+#ifdef XKEYS
+#include <term.h>
+
+#ifdef  FKEYS
+/*
+ * Get keyboard character.  Very simple if you use keymaps and keys files.
+ * Bob was right -- the old XKEYS code is not the right solution.
+ * FKEYS code is not useful other than to help debug FKEYS code in
+ * extend.c.
+ */
+
+char   *keystrings[] = {NULL};
+#endif /* FKEYS */
+
+/*
+ * Turn on function keys using keypad_xmit, then load a keys file, if
+ * available.  The keys file is located in the same manner as the startup
+ * file is, depending on what startupfile() does on your system.
+ */
+void
+ttykeymapinit(void)
+{
+       char    *cp;
+
+#ifdef FKEYS
+       /* Bind keypad function keys. */
+       if (key_left)
+               dobindkey(fundamental_map, "backward-char", key_left);
+       if (key_right)
+               dobindkey(fundamental_map, "forward-char", key_right);
+       if (key_up)
+               dobindkey(fundamental_map, "previous-line", key_up);
+       if (key_down)
+               dobindkey(fundamental_map, "next-line", key_down);
+       if (key_beg)
+               dobindkey(fundamental_map, "beginning-of-line", key_beg);
+       else if (key_home)
+               dobindkey(fundamental_map, "beginning-of-line", key_home);
+       if (key_end)
+               dobindkey(fundamental_map, "end-of-line", key_end);
+       if (key_npage)
+               dobindkey(fundamental_map, "scroll-up", key_npage);
+       if (key_ppage)
+               dobindkey(fundamental_map, "scroll-down", key_ppage);
+       if (key_dc)
+               dobindkey(fundamental_map, "delete-char", key_dc);
+#endif /* FKEYS */
+
+       if ((cp = getenv("TERM"))) {
+               if (((cp = startupfile(cp)) != NULL) && (load(cp) != TRUE))
+                       ewprintf("Error reading key initialization file");
+       }
+       if (keypad_xmit)
+               /* turn on keypad */
+               putpad(keypad_xmit, 1);
+}
+
+/*
+ * Clean up the keyboard -- called by tttidy()
+ */
+void
+ttykeymaptidy(void)
+{
+       if (keypad_local)
+               /* turn off keypad */
+               putpad(keypad_local, 1);
+}
+
+#else
+
+void
+ttykeymapinit(void)
+{
+}
+
+#endif /* XKEYS */
diff --git a/mg/tutorial b/mg/tutorial
new file mode 100644 (file)
index 0000000..f025f67
--- /dev/null
@@ -0,0 +1,349 @@
+The mg Tutorial
+---------------
+
+The mg editor is a public domain editor intended to loosely resemble GNU Emacs,
+while still retaining fast speed and a small memory footprint. 
+
+Most mg commands involve using the Control (sometimes labelled "Ctrl") or the 
+Meta (sometimes labelled "Alt") key. We will use the following conventions in
+this tutorial:
+
+  C-<chr>   means hold down the Control key while typing the character <chr>.
+  M-<chr>   means hold down the Meta key while typing the character <chr>.
+
+If you don't have a Meta key, you can use Esc instead. Press and release the
+Esc key and type <chr>. This is equivalent to M-<chr>.
+
+The first thing to learn is how to move up and down a document. To move your 
+cursor down, use the down-arrow cursor key or C-n (Control and n).
+
+>> Now type C-n multiple times and move your cursor past this line.
+
+Congratulations. You have now learned how to move your cursor down. Note how 
+mg has redrawn your screen so that the cursor is now in the middle of the 
+screen. This is a feature of mg, which allows you to see the lines before and 
+after the current cursor position.
+
+To move your cursor up, you can use the up-arrow cursor key or C-p (Control and
+p). 
+
+>> Try using C-p and C-n to move up and down and then move past this line.
+
+The next commands to learn are how to move your cursor left and right. To do 
+this, you can use the left-arrow and right-arrow cursor keys. Alternatively, 
+you can use C-b and C-f to do this.
+
+>> Practise using the arrow keys or C-b and C-f on this line.
+
+To make it easy to remember these commands, it helps to remember by letter: 
+P - Previous line, N - Next line, B - Backwards and F - Forward.
+
+Now that you've learned how to move single characters at a time, next we learn
+how to move one word at a time. To do this, you can use M-f (Meta and f) or 
+M-b (Meta and b) to move forwards and backwards, one word at a time.
+
+>> Try moving one word at a time by using M-f and M-b on this line.
+
+Notice how the Ctrl and Meta key combinations perform related functions. C-f 
+moves one letter forward, whereas M-f moves one word forward. There are many
+key combinations in mg, where C-<chr> will perform one function and M-<chr>
+will perform a similar related function.
+
+Next, we will learn how to refresh and redraw the screen.
+
+>> Now move the cursor down to this line and then type C-l (that's Control and
+   lowercase L) to refresh the screen.
+
+Note that C-l refreshes the screen and centers it on the line you typed it on.
+
+To move to the beginning or end of a line, you can use the Home and End keys,
+or you can use C-a and C-e to move to the beginning and end.
+
+>> Use C-a and C-e to move to the beginning and end of this line.
+
+The next commands we will learn is how to move up and down, one page at a time.
+To do this, you can use the Page Up (sometimes labelled PgUp) and Page Down 
+(sometimes labelled PgDn) keys. You can also use C-v and M-v to do this. C-v
+moves the cursor down one page and M-v moves it up one page.
+
+>> Try using M-v and C-v to move up and down, one page at a time.
+
+The final two motion commands we will learn are M-< (Meta-Less than) and 
+M-> (Meta-Greater than) which move you to the beginning and end of a file,
+respectively. You may not want to try that now as you will probably lose your
+place in this tutorial. Note that on most terminals, < is above the , key, so
+you'll need to press the Shift key to type <.
+
+Movement Summary
+-----------------
+
+The following is a summary of the movement commands we've learned so far:
+
+       C-f     Move forward one character (can also use right arrow key).
+       C-b     Move backward one character (can also use left arrow key).
+       C-p     Move up one line (can also use up arrow key).
+       C-n     Move down one line (can also use down arrow key).
+       M-f     Move forward one word.
+       M-b     Move backward one word.
+       C-a     Move to beginning of line (can also use Home key).
+       C-e     Move to end of line (can also use End key).
+       C-v     Move forward one page (can also use PgDn/Page Down key).
+       M-v     Move backward one page (can also use PgUp/Page Up key).
+       M-<     Move to beginning of file.
+       M->     Move to end of file.
+
+Now that you've mastered the basics of moving around in mg, you can cause mg
+to execute these commands multiple times. The way to do this is to type C-u
+followed by some digits followed by a movement command.
+
+>> Type C-u 5 C-f to move forward 5 characters.
+
+In general, C-u allows you to execute any command multiple times, not just 
+cursor motion commands. The only exception to this rule are C-v and M-v. 
+When using these two commands with an argument, they move the cursor by that 
+many lines instead of pages.
+
+Cancelling mg Commands
+----------------------
+
+If you have started typing out a command that you didn't mean to finish, you
+can use the C-g command to cancel the command immediately.
+
+>> For example, type C-u 50 and then type C-g to cancel the C-u command.
+>> Type Esc and then C-g to cancel the Esc key.
+
+In general, you can use C-g to stop any mg commands. You may type it multiple
+times if you wish. You should see the word "Quit" appear in the bottom of the
+screen when you type C-g indicating that a command was cancelled. 
+
+In general, when in doubt, use C-g to get out of trouble.
+
+
+Inserting/Deleting Text
+-----------------------
+
+To insert text anywhere, simply move your cursor to the appropriate position
+and begin typing. To delete characters, use the backspace key. If you use
+M-<backspace> (Meta and backspace key), you will delete one word instead
+of one character at a time.
+
+To delete characters to the right of the cursor, you can use C-d to delete
+characters to the right of the current position.  If you use M-d instead of
+C-d, you can delete one word at a time instead of one character at a time.
+
+>> Try inserting and deleting characters and words on this line.
+
+Note that if you type too many characters on a single line, the line will 
+scroll off the screen and you will see a $ on the line to indicate that the 
+line is too long to fit on the screen at one time.
+
+To delete a line at a time, you can use C-k to kill the line from the current 
+cursor position to the end of the line. You can type C-k multiple times to 
+kill many lines.
+
+You can issue insert or delete commands multiple times using C-u. For example,
+C-u 10 e will type out eeeeeeeeee, C-u 4 M-d will delete four words to the
+right of the cursor and so on.
+
+To undo any operation, you can use C-_ (That's control-underscore).
+
+Now if you kill something that you didn't mean to, you can yank it back from
+the dead by using C-y. In general, when you kill something bigger than a single
+character, mg saves it in a buffer somewhere and you can restore it by using
+C-y. This is useful for moving text around. You can kill text in one place,
+move your cursor to the new location and then use C-y to paste it there.
+
+Search for Text
+---------------
+
+To search for text, type C-s followed by the text you wish to search for. Note
+that as you start typing the characters, mg automatically searches as you type
+the characters.
+
+To continue searching the text you're looking for, type C-s to find the next
+instance. To search in reverse, type C-r instead of C-s. If you type C-s or 
+C-r twice, it will simply search for the last text that you searched for.
+
+To stop searching for text, simply use the cursor keys (or C-f, C-b etc.) or
+C-g to stop the search operation. 
+
+>> Use C-s foo to search for "foo" in the text. You can use C-s again to
+   find other instances of foo in the file.
+   
+Note that if a word cannot be found, it will say Failing I-search: at the 
+bottom of the screen. Typing C-s again will wrap the search around from the
+top of the file and begin searching from there.
+
+Replace Text
+------------
+
+To replace text, use M-%. You will be prompted for the text to search for and
+the text to replace it with. You will then be taken to the first instance of
+text from the current position. At this point you can do one of the following:
+       
+       y - Replace the text at this instance and search for more items.
+       n - Skip this instance and search for more items.
+       . or Enter - Stop replacing text (You can also use C-g).
+       ! - Replace all the instances without prompting at each one.
+
+>> Try replacing "frobnitz" with "zutwalt" on this line.
+
+Cut/Copy/Paste Text
+-------------------
+
+As explained above, you can cut regions using C-k to kill multiple lines. To
+paste the text that you just cut, simply move your cursor to the point and 
+then type C-y to restore the text. You may type C-y multiple times to restore
+the text. Hence, to copy text, you can use C-k to kill all the lines, use C-y
+to restore it immediately, then move to the region you want to copy it to and
+then type C-y again to restore the last cut text block again.
+
+Another way to cut or copy chunks of text is to first position your cursor at
+the starting point of the chunk of text. Then type C-<space> to mark this as
+the starting point to cut or copy. Then move the cursor to the end point of the 
+text chunk that you wish to manipulate. Then type C-w to cut the region, or 
+M-w to copy the region. If you wish to cancel marking a block of text, simply
+type C-g to cancel the operation.
+
+To paste the region that you've cut or copied above, simply move your cursor 
+to the desired location and then type C-y to paste it.
+
+Status Line
+-----------
+
+At the bottom of your screen is a reverse highlighted line. This is the status
+line and lets you know some useful information about the file you're editing.
+
+On the status line, you should see "Mg: tutorial". This lets you know that 
+you're editing a file named "tutorial". If you've edited this file and not 
+saved it, it should have a "**" to the left of those words. If this file is 
+read-only, you should see a "%%" to the left of those words. 
+
+To the right of the status line, you should see L followed by digits and C
+followed by some more digits. These indicate the line number and column number
+of the file that your cursor is currently on. If you move the cursor around,
+you should see the line and column number change.
+
+In the middle of the screen, you should see the word "(fundamental)" which 
+indicates that the current editing mode is "fundamental-mode". The mg editor
+also supports a c-mode that is more suited to editing C code. There are also
+some other useful editing modes for different situations. See the man page 
+for mg(1) to learn about the various editing modes.
+
+Opening and Saving Files
+------------------------
+
+To open a file, you can use C-x C-f. You will then be prompted for a file name.
+If you type a file name that doesn't already exist, a new file will be opened
+for you. If the file name already exists, then it will be opened for you and 
+you can begin editing it. Note that you do not need to type the whole file 
+name for an existing file. You can type part of the file name and then press
+the TAB key. If there is only file name that matches, mg will fill in the rest
+of the file name for you. If there are multiple files, mg will display that 
+the choice is ambiguous. If you type the TAB key again, mg will show you all 
+the available choices for file names.
+
+NOTE: If you type C-x f instead of C-x C-f, you can use C-g to cancel the
+Set-Fill-Column command. You can also use C-g to cancel the C-x C-f command
+if you don't wish to open a new file.
+
+To save the file once you've edited it, use C-x C-s to save the file. When
+mg is done saving the file, you should see the words "Wrote /path/to/file"
+in the bottom of your screen. In general, it is a good idea to save quite 
+often. When you save a file, mg saves a backup of the file with a tilde (~)
+character at the end.
+
+Windows
+-------
+
+The mg editor can support several windows at the same time, each one displaying
+different text. To split a screen into two horizontal windows use C-x 2 to do
+this. To return to one window, use C-x 1 to close the other windows and only
+keep the current window.
+
+>> Use C-x 2 to split the screen into two windows. 
+
+>> Use C-x o to move from one window to the other. You can scroll up and down
+   in each window using the cursor keys or C-n and C-p keys.
+
+>> Use C-x 1 to restore back to one window.
+
+Buffers
+-------
+
+The mg editor is capable of editing multiple files at the same time. When you
+open a second file with C-x C-f, the first file is still being edited by mg.
+You can list all the buffers that are opened by mg by typing C-x C-b. The 
+screen should divide into two and the top window will list the buffers that
+are currently open. Use C-x o to switch to the top window (we already learned
+this key combination above in the Windows section) and then use the arrow keys
+to move to the buffer you wish to switch to and then type the Enter key to 
+select that buffer. Then use C-x 1 to switch back to only one window.
+
+You may also move back to the last opened buffer by using C-x b to toggle back
+and forth between two buffers. Note the difference between C-x b and C-x C-b.
+
+>> Use C-x C-f to open a new file
+>> Use C-x b to switch back and forth between that buffer and this one.
+
+To edit files in multiple windows, use C-x 2 to split the screen into two 
+windows. Then use C-x C-f to open a new file in one of the two windows. You
+can then switch between the two windows using C-x o. You can switch between
+buffers in any window using C-x b. To go back to one window, use C-x 1.
+
+To kill any buffer, use C-x k. You will be prompted for the buffer to kill.
+By default, the current buffer is selected as the one to kill. You may also
+type another buffer name or use C-g to cancel the operation.
+
+Extended Commands
+-----------------
+
+The mg editor has several extended commands, more than what can be covered
+by the Control and Meta keys. The mg editor gets around this by using what is
+called the X (eXtend) command. There are two forms of this:
+
+       C-x     Character eXtension. Followed by one character.
+       M-x     Named character eXtension. Followed by a long command.
+
+You've already seen C-x C-f and C-x C-s to open and save a file. There are
+other longer commands. For instance, you can also open a file by typing
+M-x open-file Enter. When you type a command using M-x, mg prompts you for
+the command at the bottom of the screen. You can type out the whole command
+if you wish, or you can type out part of the command and then use the TAB key
+for autocompleting the command.
+
+For instance, to replace text, you can type M-x repl TAB enter to execute 
+the replace-text command. To cancel this command, type C-g. 
+
+To see a list of all available mg(1) commands, consult the man page.
+
+Exiting mg
+----------
+
+To exit mg temporarily and return to the shell, use C-z. This will take you
+back to the command shell. To return back to mg, type fg in the shell and you
+will be returned to your mg session.
+
+To exit mg permanently, type C-x C-c. If you have any unsaved buffers, you 
+will be asked if you wish to save them or not. 
+
+Conclusion
+----------
+
+This tutorial is meant to get new users up and running with mg. There is more
+information available via the mg(1) man page. If you have any suggestions for
+improvement, please don't hesitate to drop a message or (better still) submit
+a diff to tech@openbsd.org.
+
+Author Info
+-----------
+
+Original Author of this document: Mayukh Bose, 
+Date last updated: 2012-05-25
+
+Copyright
+---------
+
+None. This document is in the public domain.
+
+
diff --git a/mg/undo.c b/mg/undo.c
new file mode 100644 (file)
index 0000000..1efd2a1
--- /dev/null
+++ b/mg/undo.c
@@ -0,0 +1,585 @@
+/* $OpenBSD: undo.c,v 1.50 2010/06/30 19:12:54 oga Exp $ */
+/*
+ * This file is in the public domain
+ */
+
+#include "def.h"
+#include "kbd.h"
+
+#define MAX_FREE_RECORDS       32
+
+/*
+ * Local variables
+ */
+static struct undoq             undo_free;
+static int                      undo_free_num;
+static int                      boundary_flag = TRUE;
+static int                      undo_enable_flag = TRUE;
+
+/*
+ * Local functions
+ */
+static int find_dot(struct line *, int);
+static int find_lo(int, struct line **, int *, int *);
+static struct undo_rec *new_undo_record(void);
+static int drop_oldest_undo_record(void);
+
+/*
+ * find_dot, find_lo()
+ *
+ * Find an absolute dot in the buffer from a line/offset pair, and vice-versa.
+ *
+ * Since lines can be deleted while they are referenced by undo record, we
+ * need to have an absolute dot to have something reliable.
+ */
+static int
+find_dot(struct line *lp, int off)
+{
+       int      count = 0;
+       struct line     *p;
+
+       for (p = curbp->b_headp; p != lp; p = lforw(p)) {
+               if (count != 0) {
+                       if (p == curbp->b_headp) {
+                               ewprintf("Error: Undo stuff called with a"
+                                   "nonexistent line");
+                               return (FALSE);
+                       }
+               }
+               count += llength(p) + 1;
+       }
+       count += off;
+
+       return (count);
+}
+
+static int
+find_lo(int pos, struct line **olp, int *offset, int *lnum)
+{
+       struct line *p;
+       int lineno;
+
+       p = curbp->b_headp;
+       lineno = 0;
+       while (pos > llength(p)) {
+               pos -= llength(p) + 1;
+               if ((p = lforw(p)) == curbp->b_headp) {
+                       *olp = NULL;
+                       *offset = 0;
+                       return (FALSE);
+               }
+               lineno++;
+       }
+       *olp = p;
+       *offset = pos;
+       *lnum = lineno;
+
+       return (TRUE);
+}
+
+static struct undo_rec *
+new_undo_record(void)
+{
+       struct undo_rec *rec;
+
+       rec = TAILQ_FIRST(&undo_free);
+       if (rec != NULL) {
+               /* Remove it from the free-list */
+               TAILQ_REMOVE(&undo_free, rec, next);
+               undo_free_num--;
+       } else {
+               if ((rec = malloc(sizeof(*rec))) == NULL)
+                       panic("Out of memory in undo code (record)");
+       }
+       memset(rec, 0, sizeof(struct undo_rec));
+
+       return (rec);
+}
+
+void
+free_undo_record(struct undo_rec *rec)
+{
+       static int initialised = 0;
+
+       /*
+        * On the first run, do initialisation of the free list.
+        */
+       if (initialised == 0) {
+               TAILQ_INIT(&undo_free);
+               initialised = 1;
+       }
+       if (rec->content != NULL) {
+               free(rec->content);
+               rec->content = NULL;
+       }
+       if (undo_free_num >= MAX_FREE_RECORDS) {
+               free(rec);
+               return;
+       }
+       undo_free_num++;
+
+       TAILQ_INSERT_HEAD(&undo_free, rec, next);
+}
+
+/*
+ * Drop the oldest undo record in our list. Return 1 if we could remove it,
+ * 0 if the undo list was empty.
+ */
+static int
+drop_oldest_undo_record(void)
+{
+       struct undo_rec *rec;
+
+       rec = TAILQ_LAST(&curbp->b_undo, undoq);
+       if (rec != NULL) {
+               undo_free_num--;
+               TAILQ_REMOVE(&curbp->b_undo, rec, next);
+               free_undo_record(rec);
+               return (1);
+       }
+       return (0);
+}
+
+static int
+lastrectype(void)
+{
+       struct undo_rec *rec;
+
+       if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL)
+               return (rec->type);
+       return (0);
+}
+
+/*
+ * Returns TRUE if undo is enabled, FALSE otherwise.
+ */
+int
+undo_enabled(void)
+{
+       return (undo_enable_flag);
+}
+
+/*
+ * undo_enable: toggle undo_enable.
+ * Returns the previous value of the flag.
+ */
+int
+undo_enable(int f, int n)
+{
+       int pon = undo_enable_flag;
+
+       if (f & (FFARG | FFRAND))
+               undo_enable_flag = n > 0;
+       else
+               undo_enable_flag = !undo_enable_flag;
+
+       if (!(f & FFRAND))
+               ewprintf("Undo %sabled", undo_enable_flag ? "en" : "dis");
+
+       return (pon);
+}
+
+/*
+ * If undo is enabled, then:
+ *  Toggle undo boundary recording.
+ *  If called with an argument, (n > 0) => enable. Otherwise disable.
+ * In either case, add an undo boundary
+ * If undo is disabled, this function has no effect.
+ */
+int
+undo_boundary_enable(int f, int n)
+{
+       int bon = boundary_flag;
+
+       if (!undo_enable_flag)
+               return (FALSE);
+
+       undo_add_boundary(FFRAND, 1);
+
+       if (f & (FFARG | FFRAND))
+               boundary_flag = n > 0;
+       else
+               boundary_flag = !boundary_flag;
+
+       if (!(f & FFRAND))
+               ewprintf("Undo boundaries %sabled",
+                   boundary_flag ? "en" : "dis");
+
+       return (bon);
+}
+
+/*
+ * Record an undo boundary, unless boundary_flag == FALSE.
+ * Does nothing if previous undo entry is already a boundary or 'modified' flag.
+ */
+int
+undo_add_boundary(int f, int n)
+{
+       struct undo_rec *rec;
+       int last;
+
+       if (boundary_flag == FALSE)
+               return (FALSE);
+
+       last = lastrectype();
+       if (last == BOUNDARY || last == MODIFIED)
+               return (TRUE);
+
+       rec = new_undo_record();
+       rec->type = BOUNDARY;
+
+       TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
+
+       return (TRUE);
+}
+
+/*
+ * Record an undo "modified" boundary
+ */
+void
+undo_add_modified(void)
+{
+       struct undo_rec *rec;
+
+       rec = new_undo_record();
+       rec->type = MODIFIED;
+
+       TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
+
+       return;
+}
+
+int
+undo_add_insert(struct line *lp, int offset, int size)
+{
+       struct region   reg;
+       struct  undo_rec *rec;
+       int     pos;
+
+       if (!undo_enable_flag)
+               return (TRUE);
+       reg.r_linep = lp;
+       reg.r_offset = offset;
+       reg.r_size = size;
+
+       pos = find_dot(lp, offset);
+
+       /*
+        * We try to reuse the last undo record to `compress' things.
+        */
+       rec = TAILQ_FIRST(&curbp->b_undo);
+       if (rec != NULL && rec->type == INSERT) {
+               if (rec->pos + rec->region.r_size == pos) {
+                       rec->region.r_size += reg.r_size;
+                       return (TRUE);
+               }
+       }
+
+       /*
+        * We couldn't reuse the last undo record, so prepare a new one.
+        */
+       rec = new_undo_record();
+       rec->pos = pos;
+       rec->type = INSERT;
+       memmove(&rec->region, &reg, sizeof(struct region));
+       rec->content = NULL;
+
+       undo_add_boundary(FFRAND, 1);
+
+       TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
+
+       return (TRUE);
+}
+
+/*
+ * This of course must be done _before_ the actual deletion is done.
+ */
+int
+undo_add_delete(struct line *lp, int offset, int size, int isreg)
+{
+       struct region   reg;
+       struct  undo_rec *rec;
+       int     pos;
+
+       if (!undo_enable_flag)
+               return (TRUE);
+
+       reg.r_linep = lp;
+       reg.r_offset = offset;
+       reg.r_size = size;
+
+       pos = find_dot(lp, offset);
+
+       if (offset == llength(lp))      /* if it's a newline... */
+               undo_add_boundary(FFRAND, 1);
+       else if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL) {
+               /*
+                * Separate this command from the previous one if we're not
+                * just before the previous record...
+                */
+               if (!isreg && rec->type == DELETE) {
+                       if (rec->pos - rec->region.r_size != pos)
+                               undo_add_boundary(FFRAND, 1);
+               }
+       }
+       rec = new_undo_record();
+       rec->pos = pos;
+       if (isreg)
+               rec->type = DELREG;
+       else
+               rec->type = DELETE;
+       memmove(&rec->region, &reg, sizeof(struct region));
+       do {
+               rec->content = malloc(reg.r_size + 1);
+       } while ((rec->content == NULL) && drop_oldest_undo_record());
+
+       if (rec->content == NULL)
+               panic("Out of memory");
+
+       region_get_data(&reg, rec->content, reg.r_size);
+
+       if (isreg || lastrectype() != DELETE)
+               undo_add_boundary(FFRAND, 1);
+
+       TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next);
+
+       return (TRUE);
+}
+
+/*
+ * This of course must be called before the change takes place.
+ */
+int
+undo_add_change(struct line *lp, int offset, int size)
+{
+       if (!undo_enable_flag)
+               return (TRUE);
+       undo_add_boundary(FFRAND, 1);
+       boundary_flag = FALSE;
+       undo_add_delete(lp, offset, size, 0);
+       undo_add_insert(lp, offset, size);
+       boundary_flag = TRUE;
+       undo_add_boundary(FFRAND, 1);
+
+       return (TRUE);
+}
+
+/*
+ * Show the undo records for the current buffer in a new buffer.
+ */
+/* ARGSUSED */
+int
+undo_dump(int f, int n)
+{
+       struct   undo_rec *rec;
+       struct buffer   *bp;
+       struct mgwin    *wp;
+       char     buf[4096], tmp[1024];
+       int      num;
+
+       /*
+        * Prepare the buffer for insertion.
+        */
+       if ((bp = bfind("*undo*", TRUE)) == NULL)
+               return (FALSE);
+       bp->b_flag |= BFREADONLY;
+       bclear(bp);
+       popbuf(bp, WNONE);
+
+       for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
+               if (wp->w_bufp == bp) {
+                       wp->w_dotp = bp->b_headp;
+                       wp->w_doto = 0;
+               }
+       }
+
+       num = 0;
+       TAILQ_FOREACH(rec, &curbp->b_undo, next) {
+               num++;
+               snprintf(buf, sizeof(buf),
+                   "%d:\t %s at %d ", num,
+                   (rec->type == DELETE) ? "DELETE":
+                   (rec->type == DELREG) ? "DELREGION":
+                   (rec->type == INSERT) ? "INSERT":
+                   (rec->type == BOUNDARY) ? "----" :
+                   (rec->type == MODIFIED) ? "MODIFIED": "UNKNOWN",
+                   rec->pos);
+
+               if (rec->content) {
+                       (void)strlcat(buf, "\"", sizeof(buf));
+                       snprintf(tmp, sizeof(tmp), "%.*s", rec->region.r_size,
+                           rec->content);
+                       (void)strlcat(buf, tmp, sizeof(buf));
+                       (void)strlcat(buf, "\"", sizeof(buf));
+               }
+               snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size);
+               if (strlcat(buf, tmp, sizeof(buf)) >= sizeof(buf)) {
+                       ewprintf("Undo record too large. Aborted.");
+                       return (FALSE);
+               }
+               addlinef(bp, "%s", buf);
+       }
+       return (TRUE);
+}
+
+/*
+ * After the user did action1, then action2, then action3:
+ *
+ *     [action3] <--- Undoptr
+ *     [action2]
+ *     [action1]
+ *      ------
+ *      [undo]
+ *
+ * After undo:
+ *
+ *     [undo of action3]
+ *     [action2] <--- Undoptr
+ *     [action1]
+ *      ------
+ *      [undo]
+ *
+ * After another undo:
+ *
+ *
+ *     [undo of action2]
+ *     [undo of action3]
+ *     [action1]  <--- Undoptr
+ *      ------
+ *      [undo]
+ *
+ * Note that the "undo of actionX" have no special meaning. Only when
+ * we undo a deletion, the insertion will be recorded just as if it
+ * was typed on the keyboard. Resulting in the inverse operation being
+ * saved in the list.
+ *
+ * If undoptr reaches the bottom of the list, or if we moved between
+ * two undo actions, we make it point back at the topmost record. This is
+ * how we handle redoing.
+ */
+/* ARGSUSED */
+int
+undo(int f, int n)
+{
+       struct undo_rec *ptr, *nptr;
+       int              done, rval;
+       struct line     *lp;
+       int              offset, save, dot;
+       static int       nulled = FALSE;
+       int              lineno;
+
+       if (n < 0)
+               return (FALSE);
+
+       dot = find_dot(curwp->w_dotp, curwp->w_doto);
+
+       ptr = curbp->b_undoptr;
+
+       /* first invocation, make ptr point back to the top of the list */
+       if ((ptr == NULL && nulled == TRUE) ||  rptcount == 0) {
+               ptr = TAILQ_FIRST(&curbp->b_undo);
+               nulled = TRUE;
+       }
+
+       rval = TRUE;
+       while (n--) {
+               /* if we have a spurious boundary, free it and move on.... */
+               while (ptr && ptr->type == BOUNDARY) {
+                       nptr = TAILQ_NEXT(ptr, next);
+                       TAILQ_REMOVE(&curbp->b_undo, ptr, next);
+                       free_undo_record(ptr);
+                       ptr = nptr;
+               }
+               /*
+                * Ptr is NULL, but on the next run, it will point to the
+                * top again, redoing all stuff done in the buffer since
+                * its creation.
+                */
+               if (ptr == NULL) {
+                       ewprintf("No further undo information");
+                       rval = FALSE;
+                       nulled = TRUE;
+                       break;
+               }
+               nulled = FALSE;
+
+               /*
+                * Loop while we don't get a boundary specifying we've
+                * finished the current action...
+                */
+
+               undo_add_boundary(FFRAND, 1);
+
+               save = boundary_flag;
+               boundary_flag = FALSE;
+
+               done = 0;
+               do {
+                       /*
+                        * Move to where this has to apply
+                        *
+                        * Boundaries (and the modified flag)  are put as
+                        * position 0 (to save lookup time in find_dot)
+                        * so we must not move there...
+                        */
+                       if (ptr->type != BOUNDARY && ptr->type != MODIFIED) {
+                               if (find_lo(ptr->pos, &lp,
+                                   &offset, &lineno) == FALSE) {
+                                       ewprintf("Internal error in Undo!");
+                                       rval = FALSE;
+                                       break;
+                               }
+                               curwp->w_dotp = lp;
+                               curwp->w_doto = offset;
+                               curwp->w_markline = curwp->w_dotline;
+                               curwp->w_dotline = lineno;
+                       }
+
+                       /*
+                        * Do operation^-1
+                        */
+                       switch (ptr->type) {
+                       case INSERT:
+                               ldelete(ptr->region.r_size, KNONE);
+                               break;
+                       case DELETE:
+                               lp = curwp->w_dotp;
+                               offset = curwp->w_doto;
+                               region_put_data(ptr->content,
+                                   ptr->region.r_size);
+                               curwp->w_dotp = lp;
+                               curwp->w_doto = offset;
+                               break;
+                       case DELREG:
+                               region_put_data(ptr->content,
+                                   ptr->region.r_size);
+                               break;
+                       case BOUNDARY:
+                               done = 1;
+                               break;
+                       case MODIFIED:
+                               curbp->b_flag &= ~BFCHG;
+                               break;
+                       default:
+                               break;
+                       }
+
+                       /* And move to next record */
+                       ptr = TAILQ_NEXT(ptr, next);
+               } while (ptr != NULL && !done);
+
+               boundary_flag = save;
+               undo_add_boundary(FFRAND, 1);
+
+               ewprintf("Undo!");
+       }
+       /*
+        * Record where we are. (we have to save our new position at the end
+        * since we change the dot when undoing....)
+        */
+       curbp->b_undoptr = ptr;
+
+       curbp->b_undopos = find_dot(curwp->w_dotp, curwp->w_doto);
+
+       return (rval);
+}
diff --git a/mg/version.c b/mg/version.c
new file mode 100644 (file)
index 0000000..2dc08cd
--- /dev/null
@@ -0,0 +1,24 @@
+/*     $OpenBSD: version.c,v 1.9 2005/06/14 18:14:40 kjell Exp $       */
+
+/* This file is in the public domain. */
+
+/*
+ * This file contains the string that gets written
+ * out by the emacs-version command.
+ */
+
+#include "def.h"
+
+const char     version[] = "Mg 2a";
+
+/*
+ * Display the version. All this does
+ * is copy the version string onto the echo line.
+ */
+/* ARGSUSED */
+int
+showversion(int f, int n)
+{
+       ewprintf("%s", version);
+       return (TRUE);
+}
diff --git a/mg/window.c b/mg/window.c
new file mode 100644 (file)
index 0000000..db913c9
--- /dev/null
@@ -0,0 +1,428 @@
+/*     $OpenBSD: window.c,v 1.28 2011/08/01 12:15:23 lum Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ *             Window handling.
+ */
+
+#include "def.h"
+
+struct mgwin *
+new_window(struct buffer *bp)
+{
+       struct mgwin *wp;
+
+       wp = calloc(1, sizeof(struct mgwin));
+       if (wp == NULL)
+               return (NULL);
+
+       wp->w_bufp = bp;
+       wp->w_dotp = NULL;
+       wp->w_doto = 0;
+       wp->w_markp = NULL;
+       wp->w_marko = 0;
+       wp->w_rflag = 0;
+       wp->w_frame = 0;
+       wp->w_wrapline = NULL;
+       wp->w_dotline = wp->w_markline = 1;
+       if (bp)
+               bp->b_nwnd++;
+       return (wp);
+}
+
+/*
+ * Reposition dot in the current window to line "n".  If the argument is
+ * positive, it is that line.  If it is negative it is that line from the
+ * bottom.  If it is 0 the window is centered (this is what the standard
+ * redisplay code does).  If GOSREC is undefined, default is 0, so it acts
+ * like GNU.  If GOSREC is defined, with no argument it defaults to 1 and
+ * works like in Gosling.
+ */
+/* ARGSUSED */
+int
+reposition(int f, int n)
+{
+#ifndef GOSREC
+       curwp->w_frame = (f & FFARG) ? (n >= 0 ? n + 1 : n) : 0;
+#else /* !GOSREC */
+       curwp->w_frame = n;
+#endif /* !GOSREC */
+       curwp->w_rflag |= WFFRAME;
+       sgarbf = TRUE;
+       return (TRUE);
+}
+
+/*
+ * Refresh the display.  A call is made to the "ttresize" entry in the
+ * terminal handler, which tries to reset "nrow" and "ncol".  They will,
+ * however, never be set outside of the NROW or NCOL range.  If the display
+ * changed size, arrange that everything is redone, then call "update" to
+ * fix the display.  We do this so the new size can be displayed.  In the
+ * normal case the call to "update" in "main.c" refreshes the screen, and
+ * all of the windows need not be recomputed. This call includes a
+ * 'force' parameter to ensure that the redraw is done, even after a
+ * a suspend/continue (where the window size parameters will already
+ * be updated). Note that when you get to the "display unusable"
+ * message, the screen will be messed up. If you make the window bigger
+ * again, and send another command, everything will get fixed!
+ */
+int
+redraw(int f, int n)
+{
+       return (do_redraw(f, n, FALSE));
+}
+
+/* ARGSUSED */
+int
+do_redraw(int f, int n, int force)
+{
+       struct mgwin    *wp;
+       int              oldnrow, oldncol;
+
+       oldnrow = nrow;
+       oldncol = ncol;
+       ttresize();
+       if (nrow != oldnrow || ncol != oldncol || force) {
+
+               /* find last */
+               wp = wheadp;
+               while (wp->w_wndp != NULL)
+                       wp = wp->w_wndp;
+
+               /* check if too small */
+               if (nrow < wp->w_toprow + 3) {
+                       ewprintf("Display unusable");
+                       return (FALSE);
+               }
+               wp->w_ntrows = nrow - wp->w_toprow - 2;
+               sgarbf = TRUE;
+               update();
+       } else
+               sgarbf = TRUE;
+       return (TRUE);
+}
+
+/*
+ * The command to make the next window (next => down the screen) the current
+ * window. There are no real errors, although the command does nothing if
+ * there is only 1 window on the screen.
+ */
+/* ARGSUSED */
+int
+nextwind(int f, int n)
+{
+       struct mgwin    *wp;
+
+       if ((wp = curwp->w_wndp) == NULL)
+               wp = wheadp;
+       curwp = wp;
+       curbp = wp->w_bufp;
+       return (TRUE);
+}
+
+/* not in GNU Emacs */
+/*
+ * This command makes the previous window (previous => up the screen) the
+ * current window. There are no errors, although the command does not do
+ * a lot if there is only 1 window.
+ */
+/* ARGSUSED */
+int
+prevwind(int f, int n)
+{
+       struct mgwin    *wp1, *wp2;
+
+       wp1 = wheadp;
+       wp2 = curwp;
+       if (wp1 == wp2)
+               wp2 = NULL;
+       while (wp1->w_wndp != wp2)
+               wp1 = wp1->w_wndp;
+       curwp = wp1;
+       curbp = wp1->w_bufp;
+       return (TRUE);
+}
+
+/*
+ * This command makes the current window the only window on the screen.  Try
+ * to set the framing so that "." does not have to move on the display.  Some
+ * care has to be taken to keep the values of dot and mark in the buffer
+ * structures right if the destruction of a window makes a buffer become
+ * undisplayed.
+ */
+/* ARGSUSED */
+int
+onlywind(int f, int n)
+{
+       struct mgwin    *wp;
+       struct line     *lp;
+       int              i;
+
+       while (wheadp != curwp) {
+               wp = wheadp;
+               wheadp = wp->w_wndp;
+               if (--wp->w_bufp->b_nwnd == 0) {
+                       wp->w_bufp->b_dotp = wp->w_dotp;
+                       wp->w_bufp->b_doto = wp->w_doto;
+                       wp->w_bufp->b_markp = wp->w_markp;
+                       wp->w_bufp->b_marko = wp->w_marko;
+               }
+               free(wp);
+       }
+       while (curwp->w_wndp != NULL) {
+               wp = curwp->w_wndp;
+               curwp->w_wndp = wp->w_wndp;
+               if (--wp->w_bufp->b_nwnd == 0) {
+                       wp->w_bufp->b_dotp = wp->w_dotp;
+                       wp->w_bufp->b_doto = wp->w_doto;
+                       wp->w_bufp->b_markp = wp->w_markp;
+                       wp->w_bufp->b_marko = wp->w_marko;
+               }
+               free(wp);
+       }
+       lp = curwp->w_linep;
+       i = curwp->w_toprow;
+       while (i != 0 && lback(lp) != curbp->b_headp) {
+               --i;
+               lp = lback(lp);
+       }
+       curwp->w_toprow = 0;
+
+       /* 2 = mode, echo */
+       curwp->w_ntrows = nrow - 2;
+       curwp->w_linep = lp;
+       curwp->w_rflag |= WFMODE | WFFULL;
+       return (TRUE);
+}
+
+/*
+ * Split the current window.  A window smaller than 3 lines cannot be split.
+ * The only other error that is possible is a "malloc" failure allocating the
+ * structure for the new window.
+ * If called with a FFOTHARG, flags on the new window are set to 'n'.
+ */
+/* ARGSUSED */
+int
+splitwind(int f, int n)
+{
+       struct mgwin    *wp, *wp1, *wp2;
+       struct line     *lp;
+       int              ntru, ntrd, ntrl;
+
+       if (curwp->w_ntrows < 3) {
+               ewprintf("Cannot split a %d line window", curwp->w_ntrows);
+               return (FALSE);
+       }
+       wp = new_window(curbp);
+       if (wp == NULL) {
+               ewprintf("Unable to create a window");
+               return (FALSE);
+       }
+
+       /* use the current dot and mark */
+       wp->w_dotp = curwp->w_dotp;
+       wp->w_doto = curwp->w_doto;
+       wp->w_markp = curwp->w_markp;
+       wp->w_marko = curwp->w_marko;
+       wp->w_dotline = curwp->w_dotline;
+       wp->w_markline = curwp->w_markline;
+
+       /* figure out which half of the screen we're in */
+       ntru = (curwp->w_ntrows - 1) / 2;       /* Upper size */
+       ntrl = (curwp->w_ntrows - 1) - ntru;    /* Lower size */
+
+       for (lp = curwp->w_linep, ntrd = 0; lp != curwp->w_dotp;
+           lp = lforw(lp))
+               ntrd++;
+
+       lp = curwp->w_linep;
+
+       /* old is upper window */
+       if (ntrd <= ntru) {
+               /* hit mode line */
+               if (ntrd == ntru)
+                       lp = lforw(lp);
+               curwp->w_ntrows = ntru;
+               wp->w_wndp = curwp->w_wndp;
+               curwp->w_wndp = wp;
+               wp->w_toprow = curwp->w_toprow + ntru + 1;
+               wp->w_ntrows = ntrl;
+       /* old is lower window */
+       } else {
+               wp1 = NULL;
+               wp2 = wheadp;
+               while (wp2 != curwp) {
+                       wp1 = wp2;
+                       wp2 = wp2->w_wndp;
+               }
+               if (wp1 == NULL)
+                       wheadp = wp;
+               else
+                       wp1->w_wndp = wp;
+               wp->w_wndp = curwp;
+               wp->w_toprow = curwp->w_toprow;
+               wp->w_ntrows = ntru;
+
+               /* mode line */
+               ++ntru;
+               curwp->w_toprow += ntru;
+               curwp->w_ntrows = ntrl;
+               while (ntru--)
+                       lp = lforw(lp);
+       }
+
+       /* adjust the top lines if necessary */
+       curwp->w_linep = lp;
+       wp->w_linep = lp;
+
+       curwp->w_rflag |= WFMODE | WFFULL;
+       wp->w_rflag |= WFMODE | WFFULL;
+       /* if FFOTHARG, set flags) */
+       if (f & FFOTHARG)
+               wp->w_flag = n;
+               
+       return (TRUE);
+}
+
+/*
+ * Enlarge the current window.  Find the window that loses space.  Make sure
+ * it is big enough.  If so, hack the window descriptions, and ask redisplay
+ * to do all the hard work.  You don't just set "force reframe" because dot
+ * would move.
+ */
+/* ARGSUSED */
+int
+enlargewind(int f, int n)
+{
+       struct mgwin    *adjwp;
+       struct line     *lp;
+       int              i;
+
+       if (n < 0)
+               return (shrinkwind(f, -n));
+       if (wheadp->w_wndp == NULL) {
+               ewprintf("Only one window");
+               return (FALSE);
+       }
+       if ((adjwp = curwp->w_wndp) == NULL) {
+               adjwp = wheadp;
+               while (adjwp->w_wndp != curwp)
+                       adjwp = adjwp->w_wndp;
+       }
+       if (adjwp->w_ntrows <= n) {
+               ewprintf("Impossible change");
+               return (FALSE);
+       }
+
+       /* shrink below */
+       if (curwp->w_wndp == adjwp) {
+               lp = adjwp->w_linep;
+               for (i = 0; i < n && lp != adjwp->w_bufp->b_headp; ++i)
+                       lp = lforw(lp);
+               adjwp->w_linep = lp;
+               adjwp->w_toprow += n;
+       /* shrink above */
+       } else {
+               lp = curwp->w_linep;
+               for (i = 0; i < n && lback(lp) != curbp->b_headp; ++i)
+                       lp = lback(lp);
+               curwp->w_linep = lp;
+               curwp->w_toprow -= n;
+       }
+       curwp->w_ntrows += n;
+       adjwp->w_ntrows -= n;
+       curwp->w_rflag |= WFMODE | WFFULL;
+       adjwp->w_rflag |= WFMODE | WFFULL;
+       return (TRUE);
+}
+
+/*
+ * Shrink the current window.  Find the window that gains space.  Hack at the
+ * window descriptions. Ask the redisplay to do all the hard work.
+ */
+int
+shrinkwind(int f, int n)
+{
+       struct mgwin    *adjwp;
+       struct line     *lp;
+       int              i;
+
+       if (n < 0)
+               return (enlargewind(f, -n));
+       if (wheadp->w_wndp == NULL) {
+               ewprintf("Only one window");
+               return (FALSE);
+       }
+       /*
+        * Bit of flakiness - KRANDOM means it was an internal call, and
+        * to be trusted implicitly about sizes.
+        */
+       if (!(f & FFRAND) && curwp->w_ntrows <= n) {
+               ewprintf("Impossible change");
+               return (FALSE);
+       }
+       if ((adjwp = curwp->w_wndp) == NULL) {
+               adjwp = wheadp;
+               while (adjwp->w_wndp != curwp)
+                       adjwp = adjwp->w_wndp;
+       }
+
+       /* grow below */
+       if (curwp->w_wndp == adjwp) {
+               lp = adjwp->w_linep;
+               for (i = 0; i < n && lback(lp) != adjwp->w_bufp->b_headp; ++i)
+                       lp = lback(lp);
+               adjwp->w_linep = lp;
+               adjwp->w_toprow -= n;
+       /* grow above */
+       } else {
+               lp = curwp->w_linep;
+               for (i = 0; i < n && lp != curbp->b_headp; ++i)
+                       lp = lforw(lp);
+               curwp->w_linep = lp;
+               curwp->w_toprow += n;
+       }
+       curwp->w_ntrows -= n;
+       adjwp->w_ntrows += n;
+       curwp->w_rflag |= WFMODE | WFFULL;
+       adjwp->w_rflag |= WFMODE | WFFULL;
+       return (TRUE);
+}
+
+/*
+ * Delete current window. Call shrink-window to do the screen updating, then
+ * throw away the window.
+ */
+/* ARGSUSED */
+int
+delwind(int f, int n)
+{
+       struct mgwin    *wp, *nwp;
+
+       wp = curwp;             /* Cheap...              */
+
+       /* shrinkwind returning false means only one window... */
+       if (shrinkwind(FFRAND, wp->w_ntrows + 1) == FALSE)
+               return (FALSE);
+       if (--wp->w_bufp->b_nwnd == 0) {
+               wp->w_bufp->b_dotp = wp->w_dotp;
+               wp->w_bufp->b_doto = wp->w_doto;
+               wp->w_bufp->b_markp = wp->w_markp;
+               wp->w_bufp->b_marko = wp->w_marko;
+       }
+
+       /* since shrinkwind did't crap out, we know we have a second window */
+       if (wp == wheadp)
+               wheadp = curwp = wp->w_wndp;
+       else if ((curwp = wp->w_wndp) == NULL)
+               curwp = wheadp;
+       curbp = curwp->w_bufp;
+       for (nwp = wheadp; nwp != NULL; nwp = nwp->w_wndp)
+               if (nwp->w_wndp == wp) {
+                       nwp->w_wndp = wp->w_wndp;
+                       break;
+               }
+       free(wp);
+       return (TRUE);
+}
diff --git a/mg/word.c b/mg/word.c
new file mode 100644 (file)
index 0000000..c18b620
--- /dev/null
+++ b/mg/word.c
@@ -0,0 +1,346 @@
+/*     $OpenBSD: word.c,v 1.15 2008/09/15 16:11:35 kjell Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ *             Word mode commands.
+ * The routines in this file implement commands that work word at a time.
+ * There are all sorts of word mode commands.
+ */
+
+#include "def.h"
+
+RSIZE  countfword(void);
+
+/*
+ * Move the cursor backward by "n" words. All of the details of motion are
+ * performed by the "backchar" and "forwchar" routines.
+ */
+/* ARGSUSED */
+int
+backword(int f, int n)
+{
+       if (n < 0)
+               return (forwword(f | FFRAND, -n));
+       if (backchar(FFRAND, 1) == FALSE)
+               return (FALSE);
+       while (n--) {
+               while (inword() == FALSE) {
+                       if (backchar(FFRAND, 1) == FALSE)
+                               return (TRUE);
+               }
+               while (inword() != FALSE) {
+                       if (backchar(FFRAND, 1) == FALSE)
+                               return (TRUE);
+               }
+       }
+       return (forwchar(FFRAND, 1));
+}
+
+/*
+ * Move the cursor forward by the specified number of words.  All of the
+ * motion is done by "forwchar".
+ */
+/* ARGSUSED */
+int
+forwword(int f, int n)
+{
+       if (n < 0)
+               return (backword(f | FFRAND, -n));
+       while (n--) {
+               while (inword() == FALSE) {
+                       if (forwchar(FFRAND, 1) == FALSE)
+                               return (TRUE);
+               }
+               while (inword() != FALSE) {
+                       if (forwchar(FFRAND, 1) == FALSE)
+                               return (TRUE);
+               }
+       }
+       return (TRUE);
+}
+
+/*
+ * Move the cursor forward by the specified number of words.  As you move,
+ * convert any characters to upper case.
+ */
+/* ARGSUSED */
+int
+upperword(int f, int n)
+{
+       int     c, s;
+       RSIZE   size;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read-only");
+               return (FALSE);
+       }
+
+       if (n < 0)
+               return (FALSE);
+       while (n--) {
+               while (inword() == FALSE) {
+                       if (forwchar(FFRAND, 1) == FALSE)
+                               return (TRUE);
+               }
+               size = countfword();
+               undo_add_change(curwp->w_dotp, curwp->w_doto, size);
+
+               while (inword() != FALSE) {
+                       c = lgetc(curwp->w_dotp, curwp->w_doto);
+                       if (ISLOWER(c) != FALSE) {
+                               c = TOUPPER(c);
+                               lputc(curwp->w_dotp, curwp->w_doto, c);
+                               lchange(WFFULL);
+                       }
+                       if (forwchar(FFRAND, 1) == FALSE)
+                               return (TRUE);
+               }
+       }
+       return (TRUE);
+}
+
+/*
+ * Move the cursor forward by the specified number of words.  As you move
+ * convert characters to lower case.
+ */
+/* ARGSUSED */
+int
+lowerword(int f, int n)
+{
+       int     c, s;
+       RSIZE   size;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read-only");
+               return (FALSE);
+       }
+       if (n < 0)
+               return (FALSE);
+       while (n--) {
+               while (inword() == FALSE) {
+                       if (forwchar(FFRAND, 1) == FALSE)
+                               return (TRUE);
+               }
+               size = countfword();
+               undo_add_change(curwp->w_dotp, curwp->w_doto, size);
+
+               while (inword() != FALSE) {
+                       c = lgetc(curwp->w_dotp, curwp->w_doto);
+                       if (ISUPPER(c) != FALSE) {
+                               c = TOLOWER(c);
+                               lputc(curwp->w_dotp, curwp->w_doto, c);
+                               lchange(WFFULL);
+                       }
+                       if (forwchar(FFRAND, 1) == FALSE)
+                               return (TRUE);
+               }
+       }
+       return (TRUE);
+}
+
+/*
+ * Move the cursor forward by the specified number of words.  As you move
+ * convert the first character of the word to upper case, and subsequent
+ * characters to lower case.  Error if you try to move past the end of the
+ * buffer.
+ */
+/* ARGSUSED */
+int
+capword(int f, int n)
+{
+       int     c, s;
+       RSIZE   size;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read-only");
+               return (FALSE);
+       }
+
+       if (n < 0)
+               return (FALSE);
+       while (n--) {
+               while (inword() == FALSE) {
+                       if (forwchar(FFRAND, 1) == FALSE)
+                               return (TRUE);
+               }
+               size = countfword();
+               undo_add_change(curwp->w_dotp, curwp->w_doto, size);
+
+               if (inword() != FALSE) {
+                       c = lgetc(curwp->w_dotp, curwp->w_doto);
+                       if (ISLOWER(c) != FALSE) {
+                               c = TOUPPER(c);
+                               lputc(curwp->w_dotp, curwp->w_doto, c);
+                               lchange(WFFULL);
+                       }
+                       if (forwchar(FFRAND, 1) == FALSE)
+                               return (TRUE);
+                       while (inword() != FALSE) {
+                               c = lgetc(curwp->w_dotp, curwp->w_doto);
+                               if (ISUPPER(c) != FALSE) {
+                                       c = TOLOWER(c);
+                                       lputc(curwp->w_dotp, curwp->w_doto, c);
+                                       lchange(WFFULL);
+                               }
+                               if (forwchar(FFRAND, 1) == FALSE)
+                                       return (TRUE);
+                       }
+               }
+       }
+       return (TRUE);
+}
+
+/*
+ * Count characters in word, from current position
+ */
+RSIZE
+countfword()
+{
+       RSIZE            size;
+       struct line     *dotp;
+       int              doto;
+
+       dotp = curwp->w_dotp;
+       doto = curwp->w_doto;
+       size = 0;
+
+       while (inword() != FALSE) {
+               if (forwchar(FFRAND, 1) == FALSE)
+                       /* hit the end of the buffer */
+                       goto out;
+               ++size;
+       }
+out:
+       curwp->w_dotp = dotp;
+       curwp->w_doto = doto;
+       return (size);
+}
+
+
+/*
+ * Kill forward by "n" words.
+ */
+/* ARGSUSED */
+int
+delfword(int f, int n)
+{
+       RSIZE            size;
+       struct line     *dotp;
+       int              doto;
+       int s;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read-only");
+               return (FALSE);
+       }
+       if (n < 0)
+               return (FALSE);
+
+       /* purge kill buffer */
+       if ((lastflag & CFKILL) == 0)
+               kdelete();
+
+       thisflag |= CFKILL;
+       dotp = curwp->w_dotp;
+       doto = curwp->w_doto;
+       size = 0;
+
+       while (n--) {
+               while (inword() == FALSE) {
+                       if (forwchar(FFRAND, 1) == FALSE)
+                               /* hit the end of the buffer */
+                               goto out;
+                       ++size;
+               }
+               while (inword() != FALSE) {
+                       if (forwchar(FFRAND, 1) == FALSE)
+                               /* hit the end of the buffer */
+                               goto out;
+                       ++size;
+               }
+       }
+out:
+       curwp->w_dotp = dotp;
+       curwp->w_doto = doto;
+       return (ldelete(size, KFORW));
+}
+
+/*
+ * Kill backwards by "n" words.  The rules for success and failure are now
+ * different, to prevent strange behavior at the start of the buffer.  The
+ * command only fails if something goes wrong with the actual delete of the
+ * characters.  It is successful even if no characters are deleted, or if you
+ * say delete 5 words, and there are only 4 words left.  I considered making
+ * the first call to "backchar" special, but decided that that would just be
+ * weird. Normally this is bound to "M-Rubout" and to "M-Backspace".
+ */
+/* ARGSUSED */
+int
+delbword(int f, int n)
+{
+       RSIZE   size;
+       int s;
+
+       if ((s = checkdirty(curbp)) != TRUE)
+               return (s);
+       if (curbp->b_flag & BFREADONLY) {
+               ewprintf("Buffer is read-only");
+               return (FALSE);
+       }
+
+       if (n < 0)
+               return (FALSE);
+
+       /* purge kill buffer */
+       if ((lastflag & CFKILL) == 0)
+               kdelete();
+       thisflag |= CFKILL;
+       if (backchar(FFRAND, 1) == FALSE)
+               /* hit buffer start */
+               return (TRUE);
+
+       /* one deleted */
+       size = 1;
+       while (n--) {
+               while (inword() == FALSE) {
+                       if (backchar(FFRAND, 1) == FALSE)
+                               /* hit buffer start */
+                               goto out;
+                       ++size;
+               }
+               while (inword() != FALSE) {
+                       if (backchar(FFRAND, 1) == FALSE)
+                               /* hit buffer start */
+                               goto out;
+                       ++size;
+               }
+       }
+       if (forwchar(FFRAND, 1) == FALSE)
+               return (FALSE);
+
+       /* undo assumed delete */
+       --size;
+out:
+       return (ldelete(size, KBACK));
+}
+
+/*
+ * Return TRUE if the character at dot is a character that is considered to be
+ * part of a word. The word character list is hard coded. Should be settable.
+ */
+int
+inword(void)
+{
+       /* can't use lgetc in ISWORD due to bug in OSK cpp */
+       return (curwp->w_doto != llength(curwp->w_dotp) &&
+           ISWORD(curwp->w_dotp->l_text[curwp->w_doto]));
+}
diff --git a/mg/yank.c b/mg/yank.c
new file mode 100644 (file)
index 0000000..d408286
--- /dev/null
+++ b/mg/yank.c
@@ -0,0 +1,264 @@
+/*     $OpenBSD: yank.c,v 1.10 2011/07/15 16:50:52 deraadt Exp $       */
+
+/* This file is in the public domain. */
+
+/*
+ *     kill ring functions
+ */
+
+#include "def.h"
+
+#include <string.h>
+
+#ifndef KBLOCK
+#define KBLOCK 256             /* Kill buffer block size.       */
+#endif
+
+static char    *kbufp = NULL;  /* Kill buffer data.             */
+static RSIZE    kused = 0;     /* # of bytes used in KB.        */
+static RSIZE    ksize = 0;     /* # of bytes allocated in KB.   */
+static RSIZE    kstart = 0;    /* # of first used byte in KB.   */
+
+static int      kgrow(int);
+
+/*
+ * Delete all of the text saved in the kill buffer.  Called by commands when
+ * a new kill context is created. The kill buffer array is released, just in
+ * case the buffer has grown to an immense size.  No errors.
+ */
+void
+kdelete(void)
+{
+       if (kbufp != NULL) {
+               free(kbufp);
+               kbufp = NULL;
+               kstart = kused = ksize = 0;
+       }
+}
+
+/*
+ * Insert a character to the kill buffer, enlarging the buffer if there
+ * isn't any room. Always grow the buffer in chunks, on the assumption
+ * that if you put something in the kill buffer you are going to put more
+ * stuff there too later. Return TRUE if all is well, and FALSE on errors.
+ * Print a message on errors.  Dir says whether to put it at back or front.
+ * This call is ignored if  KNONE is set.
+ */
+int
+kinsert(int c, int dir)
+{
+       if (dir == KNONE)
+               return (TRUE);
+       if (kused == ksize && dir == KFORW && kgrow(dir) == FALSE)
+               return (FALSE);
+       if (kstart == 0 && dir == KBACK && kgrow(dir) == FALSE)
+               return (FALSE);
+       if (dir == KFORW)
+               kbufp[kused++] = c;
+       else if (dir == KBACK)
+               kbufp[--kstart] = c;
+       else
+               panic("broken kinsert call");   /* Oh shit! */
+       return (TRUE);
+}
+
+/*
+ * kgrow - just get more kill buffer for the callee. If dir = KBACK
+ * we are trying to get space at the beginning of the kill buffer.
+ */
+static int
+kgrow(int dir)
+{
+       int      nstart;
+       char    *nbufp;
+
+       if ((unsigned)(ksize + KBLOCK) <= (unsigned)ksize) {
+               /* probably 16 bit unsigned */
+               ewprintf("Kill buffer size at maximum");
+               return (FALSE);
+       }
+       if ((nbufp = malloc((unsigned)(ksize + KBLOCK))) == NULL) {
+               ewprintf("Can't get %ld bytes", (long)(ksize + KBLOCK));
+               return (FALSE);
+       }
+       nstart = (dir == KBACK) ? (kstart + KBLOCK) : (KBLOCK / 4);
+       bcopy(&(kbufp[kstart]), &(nbufp[nstart]), (int)(kused - kstart));
+       if (kbufp != NULL)
+               free(kbufp);
+       kbufp = nbufp;
+       ksize += KBLOCK;
+       kused = kused - kstart + nstart;
+       kstart = nstart;
+       return (TRUE);
+}
+
+/*
+ * This function gets characters from the kill buffer. If the character
+ * index "n" is off the end, it returns "-1". This lets the caller just
+ * scan along until it gets a "-1" back.
+ */
+int
+kremove(int n)
+{
+       if (n < 0 || n + kstart >= kused)
+               return (-1);
+       return (CHARMASK(kbufp[n + kstart]));
+}
+
+/*
+ * Copy a string into the kill buffer. kflag gives direction.
+ * if KNONE, do nothing.
+ */
+int
+kchunk(char *cp1, RSIZE chunk, int kflag)
+{
+       /*
+        * HACK - doesn't matter, and fixes back-over-nl bug for empty
+        *      kill buffers.
+        */
+       if (kused == kstart)
+               kflag = KFORW;
+
+       if (kflag & KFORW) {
+               while (ksize - kused < chunk)
+                       if (kgrow(kflag) == FALSE)
+                               return (FALSE);
+               bcopy(cp1, &(kbufp[kused]), (int)chunk);
+               kused += chunk;
+       } else if (kflag & KBACK) {
+               while (kstart < chunk)
+                       if (kgrow(kflag) == FALSE)
+                               return (FALSE);
+               bcopy(cp1, &(kbufp[kstart - chunk]), (int)chunk);
+               kstart -= chunk;
+       }
+
+       return (TRUE);
+}
+
+/*
+ * Kill line.  If called without an argument, it kills from dot to the end
+ * of the line, unless it is at the end of the line, when it kills the
+ * newline.  If called with an argument of 0, it kills from the start of the
+ * line to dot.  If called with a positive argument, it kills from dot
+ * forward over that number of newlines.  If called with a negative argument
+ * it kills any text before dot on the current line, then it kills back
+ * abs(arg) lines.
+ */
+/* ARGSUSED */
+int
+killline(int f, int n)
+{
+       struct line     *nextp;
+       RSIZE    chunk;
+       int      i, c;
+
+       /* clear kill buffer if last wasn't a kill */
+       if ((lastflag & CFKILL) == 0)
+               kdelete();
+       thisflag |= CFKILL;
+       if (!(f & FFARG)) {
+               for (i = curwp->w_doto; i < llength(curwp->w_dotp); ++i)
+                       if ((c = lgetc(curwp->w_dotp, i)) != ' ' && c != '\t')
+                               break;
+               if (i == llength(curwp->w_dotp))
+                       chunk = llength(curwp->w_dotp) - curwp->w_doto + 1;
+               else {
+                       chunk = llength(curwp->w_dotp) - curwp->w_doto;
+                       if (chunk == 0)
+                               chunk = 1;
+               }
+       } else if (n > 0) {
+               chunk = llength(curwp->w_dotp) - curwp->w_doto;
+               nextp = lforw(curwp->w_dotp);
+               if (nextp != curbp->b_headp)
+                       chunk++;                /* newline */
+               if (nextp == curbp->b_headp)
+                       goto done;              /* EOL */
+               i = n;
+               while (--i) {
+                       chunk += llength(nextp);
+                       nextp = lforw(nextp);
+                       if (nextp != curbp->b_headp)
+                               chunk++;        /* newline */
+                       if (nextp == curbp->b_headp)
+                               break;          /* EOL */
+               }
+       } else {
+               /* n <= 0 */
+               chunk = curwp->w_doto;
+               curwp->w_doto = 0;
+               i = n;
+               while (i++) {
+                       if (lforw(curwp->w_dotp))
+                               chunk++;
+                       curwp->w_dotp = lback(curwp->w_dotp);
+                       curwp->w_rflag |= WFMOVE;
+                       chunk += llength(curwp->w_dotp);
+               }
+       }
+       /*
+        * KFORW here is a bug.  Should be KBACK/KFORW, but we need to
+        * rewrite the ldelete code (later)?
+        */
+done:
+       if (chunk)
+               return (ldelete(chunk, KFORW));
+       return (TRUE);
+}
+
+/*
+ * Yank text back from the kill buffer.  This is really easy.  All of the work
+ * is done by the standard insert routines.  All you do is run the loop, and
+ * check for errors.  The blank lines are inserted with a call to "newline"
+ * instead of a call to "lnewline" so that the magic stuff that happens when
+ * you type a carriage return also happens when a carriage return is yanked
+ * back from the kill buffer.  An attempt has been made to fix the cosmetic
+ * bug associated with a yank when dot is on the top line of the window
+ * (nothing moves, because all of the new text landed off screen).
+ */
+/* ARGSUSED */
+int
+yank(int f, int n)
+{
+       struct line     *lp;
+       int      c, i, nline;
+
+       if (n < 0)
+               return (FALSE);
+
+       /* newline counting */
+       nline = 0;
+
+       undo_boundary_enable(FFRAND, 0);
+       while (n--) {
+               /* mark around last yank */
+               isetmark();
+               i = 0;
+               while ((c = kremove(i)) >= 0) {
+                       if (c == '\n') {
+                               if (newline(FFRAND, 1) == FALSE)
+                                       return (FALSE);
+                               ++nline;
+                       } else {
+                               if (linsert(1, c) == FALSE)
+                                       return (FALSE);
+                       }
+                       ++i;
+               }
+       }
+       /* cosmetic adjustment */
+       lp = curwp->w_linep;
+
+       /* if offscreen insert */
+       if (curwp->w_dotp == lp) {
+               while (nline-- && lback(lp) != curbp->b_headp)
+                       lp = lback(lp);
+               /* adjust framing */
+               curwp->w_linep = lp;
+               curwp->w_rflag |= WFFULL;
+       }
+       undo_boundary_enable(FFRAND, 1);
+       return (TRUE);
+}
+