diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 2000-02-25 19:08:53 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 2000-02-25 19:08:53 +0000 |
commit | 998d769a0cf8bef7d4ca0d26945c151a23b542ec (patch) | |
tree | fe53a083eaa06a2bf7631453e18a161a86ad9d62 | |
parent | b0226ecd4460819556afd27fd575d64421fd0f68 (diff) |
initial import of mg2a
50 files changed, 19457 insertions, 0 deletions
diff --git a/usr.bin/mg/Makefile b/usr.bin/mg/Makefile new file mode 100644 index 00000000000..af0fca401aa --- /dev/null +++ b/usr.bin/mg/Makefile @@ -0,0 +1,133 @@ +# Makefile for MicroEMACS. +# Is there a better way to do the rebuilds, other than using +# the links? + +SYS = sysv +LIBS = +# CDEFS gets defines, and gets passed to lint. CFLAGS gets flags, and doesn't +# get passed to lint. +# +# (Common) compile-time options: +# +# DO_METAKEY -- if bit 7 is set for a key, treat like a META key +# STARTUP -- look for and handle initialization file +# XKEYS -- use termcap function key definitions. Warning - +# XKEYS and bsmap mode do _not_ get along. +# BACKUP -- enable "make-backup-files" +# PREFIXREGION -- enable function "prefix-region" +# REGEX -- create regular expression functions +# +#CDEFS = -DDO_METAKEY +CDEFS = -DDO_METAKEY -DPREFIXREGION +CFLAGS = $(CDEFS) +CFLAGSNOO = $(CDEFS) + +# Objects which only depend on the "standard" includes +OBJS = basic.o dir.o dired.o file.o line.o match.o paragraph.o \ + random.o region.o search.o version.o window.o word.o + +# Those with unique requirements +IND = buffer.o display.o echo.o extend.o help.o kbd.o keymap.o \ + macro.o main.o modes.o regex.o re_search.o + +# System dependent objects +OOBJS = cinfo.o spawn.o ttyio.o tty.o ttykbd.o + +OBJ = $(OBJS) $(IND) $(OOBJS) fileio.o + +OSRCS = cinfo.c fileio.c spawn.c ttyio.c tty.c ttykbd.c +SRCS = basic.c dir.c dired.c file.c line.c match.c paragraph.c \ + random.c region.c search.c version.c window.c word.c \ + buffer.c display.c echo.c extend.c help.c kbd.c keymap.c \ + macro.c main.c modes.c regex.c re_search.c + +OINCS = ttydef.h sysdef.h chrdef.h +INCS = def.h + +mg: $(OBJ) + cc $(CFLAGS) -o mg $(OBJ) $(LIBS) + +tar: + tar -c -X tar.exclude -f mg.tar . + +# strip mg once you're satisfied it'll run -- makes it much smaller +strip: + strip mg + +lint: $(SRCS) $(OSRCS) $(INCS) $(OINCS) + lint -ahbz $(CDEFS) $(SRCS) $(OSRCS) + +# routines that can't be compiled optimized +# region causes the optimizer to blow up +# region.o: region.c +# cc $(CFLAGSNOO) -c region.c + +# echo blows up when compiled optimized. +# echo.o: echo.c +# cc $(CFLAGSNOO) -c echo.c + +$(OBJ): $(INCS) $(OINCS) + + +dir.r search.o: $(INCS) $(OINCS) + +regex.o re_search.o: $(INCS) $(OINCS) regex.h + +kbd.o: $(INCS) $(OINCS) macro.h kbd.h key.h + +macro.o main.o: $(INCS) $(OINCS) macro.h + +buffer.o display.o keymap.o help.o modes.o dired.o fileio.o: \ + $(INCS) $(OINCS) kbd.h + +extend.o: $(INCS) $(OINCS) kbd.h macro.h key.h + +help.o: $(INCS) $(OINCS) kbd.h key.h macro.h + +echo.o: $(INCS) $(OINCS) key.h macro.h + +$(OOBJS): $(INCS) $(OINCS) + +# sysdef.h: sys/$(SYS)/sysdef.h # Update links, if needed. +# rm -f sysdef.h +# ln sys/$(SYS)/sysdef.h . + +# ttydef.h: sys/default/ttydef.h +# rm -f ttydef.h +# ln sys/default/ttydef.h . + +# chrdef.h: sys/default/chrdef.h +# rm -f chrdef.h +# ln sys/default/chrdef.h . + +# fileio.c: sys/$(SYS)/fileio.c +# rm -f fileio.c +# ln sys/$(SYS)/fileio.c . + +# spawn.c: sys/$(SYS)/spawn.c +# rm -f spawn.c +# ln sys/$(SYS)/spawn.c . + +# tty.c: sys/default/tty.c +# rm -f tty.c +# ln sys/default/tty.c . + +# ttyio.c: sys/$(SYS)/ttyio.c +# rm -f ttyio.c +# ln sys/$(SYS)/ttyio.c . + +# ttykbd.c: sys/default/ttykbd.c +# rm -f ttykbd.c +# ln sys/default/ttykbd.c . + +# cinfo.c: sys/default/cinfo.c +# rm -f cinfo.c +# ln sys/default/cinfo.c . + +# port: $(SRCS) $(INCS) +# rm -f port +# tar cfb port 1 $? + +# clean:; rm -f $(OBJ) $(OSRCS) $(OINCS) +clean:; rm -f $(OBJ) + diff --git a/usr.bin/mg/README b/usr.bin/mg/README new file mode 100644 index 00000000000..e37a3c26b06 --- /dev/null +++ b/usr.bin/mg/README @@ -0,0 +1,197 @@ +Mg 2a README May 15, 1988 + +Mg (mg) is a Public Domain EMACS style editor. It is "broadly" +compatible with GNU Emacs, the latest creation of Richard M. +Stallman, Chief GNUisance and inventor of Emacs. GNU Emacs (and other +portions of GNU as they are released) are essentially free, (there are +handling charges for obtaining it) and so is Mg. You may never have +to learn another editor. (But probably will, at least long enough to +port Mg...) Mg was formerly named MicroGnuEmacs, the name change was +done at the request of Richard Stallman. + +Mg is not associated with the GNU project, and most of it does not +have the copyright restrictions present in GNU Emacs. (However, some +of the system dependent modules and the regular expression module do +have copyright notices, specificly the VMS/primos termcap routines and +the amiga specific routines. Look at the source code for exact +copyright restrictions.) The Mg authors individually may or may not +agree with the opinions expressed by Richard Stallman in "The GNU +Manifesto". + +To avoid GNU copyright restrictions, replace the re_search.c, regex.h +and regex.c files with empty files. + +Documentation of Mg is in the TeX file mg.tex. This should be +formatted with the TeX text formatter and printed. A start twords a mg +programmers guied in in mgprog.doc, and some of the changes from 1b +are mentioned briefly in mg2a.change. + +This program is intended to be a small, fast, and portable editor for +people who can't (or don't want to) run real Emacs thing for one +reason or another. It is compatible with GNU because there shouldn't +be any reason to learn more than one Emacs flavor. We have excised +most MicroEMACS features that were incompatible with the big brother, +and added missing features that seemed essential. + +There are at least two other major versions of MicroEMACS in +circulation. One comes from Daniel Lawrence, (based on an old version +from Dave Conroy) and is several versions have been posted to usenet. +It uses a 3.x version numbering scheme, and the latest I know about is +3.9i. It has some features not found in Mg, missing others, is +bigger, and is incompatible with GNU Emacs. It might be a better +choice for you if you *must* have something not present here and can't +run GNU. + +Another variety uses a different numbering scheme, and is up to v30. +This also comes from mod.sources, and is the latest version from the +original MicroEMACS author Dave Conroy. Mg is derived from this +version, and for the most part has replaced it. + +Mg is continuing to diverge from other MicroEmacs varients. +Significant modifacations would me nessisary to adapt code from either +the 3.x strains or v30. Command functions and key mapping, for +instance, are completely different. + +This is the third distribution release of Mg. (It went through four +beta releases to iron out the changes made by the various authors.) +Prior releases were known as MicroGnuEmacs 1a and MicroGnuEmacs 1b. +Beyond the work of Dave Conroy, author of the original public domain +v30, the current version contains the work of: + + blarson@ecla.usc.edu Bob Larson + mic@emx.utexas.edu Mic Kaczmarczik + mwm@violet.berkeley.edu Mike Meyer + sandra@cs.utah.edu Sandra Loosemore + mp1u+@andrew.cmu.edu Michael Portuesi + RCKG01M@CALSTATE.BITNET Stephen Walton + hakanson@mist.cs.orst.edu Marion Hakanson + +People who have worked on previos versions of Mg: + + rtech!daveb@sun.com Dave Brower + +These systems are known to work in the current version: + + 4.2 & 4.3 BSD Unix, SunOs 3.2, Ultrix-32 + System V + OS9/68k + VMS + Amiga + Primos + Atari ST + +Ms-Dos support is planned, but did not get done in time for this +release. (Jeff Siegal <jbs@eddie.mit.edu> was the one doing it.) +The Ms-Dos files will probably be distributed seperatly when it +becomes available. + +Cpm/68k support was dropped due to compiler bugs. Eunice support was +dropped because of lack of interest. Mg 1b does support those +systems. + +One change to late to make it into mg.tex is readding bsmap-mode (only +if BSMAP is #defined when compiling). This is a toggle that controls +input mapping to exchange the ^H (backspace) and DEL characters. Like +GNU emacs input keymaps, it is not displayed on the mode line and will +cause them to be treated as each other for echoing. (With bsmap-mode +enabled, DEL will echo ^H in the echo line.) + + +How to Make a Mg +--------------------------- + +On UNIX at least, it's easy. (Note that even on these systems you may +want to change a compile time option.) If you have BSD UNIX, do: + + ln sys/bsd/Makefile . + make + +For System V, do: + + ln sys/sysv/Makefile . + make + +There are several other directories under sys: osk, vms, amiga, atari, +prime. You should follow the directions contained therein to make one +of those versions. + +For most systems (everyting except the amiga, and atari currently), +the termcap terminal definition is used. There is a readme file in +the default subdirectory of the sys directory explaining what entries +are used and how. (Termcap is a way to do display manipulation in a +terminal independent manner.) Besides the normal startup file (usually +.mg) terminal specific initialization files may be used. (For +example, in .mg.vt100 you may want to (global-set-key "\e[A" +'previous-line) to have the up arrow key work.) + +Some changes made to make this version more like Gnu Emacs may break +startup files. Gnu Emacs 18 has both backward-delete-char and +delete-backward-char that apperently do the same thing. This version +has only the latter because that is what is documented in my manual +(version 17) and bound by Gnu Emacs to DEL. + +---------------------------------------------------------------------- + +Known limitaions: + +Recursive bindings may cause help and key rebinding code to go into +an infinite loop, aborting with a stack overflow. + +Overwrite mode does not work in macros. (Characters are inserted +rather than overwriting.) + +Dired mode has some problems: Rename does not update the buffer. +Doing a dired again will update the buffer (whether it needs it or +not) and will lose any marks for deletion. .. and . are not +recognized as special cases. + +On systems with 16 bit integers, the kill buffer cannot exceed 32767 +bytes. + + + +New implementation oddities: + +insert and define-key are new commands corresponding to the mocklisp +functions in Gnu Emacs. (Mg does not have non-command functions.) +(Mg's insert will only insert one string.) + +The display wrap code does not work at all like that of GNU emacs. + +------------------------------------------------------------------------ + +If you have a change to make that you think should be incorporated +into the next version of Mg, send it the mg-support mail +list. Addresses are: + + mg-support%ais1@ecla.usc.edu + {cit-vax,sdcrdcf,trwrb}!oberon!ais1!mg-support + +Support for additional systems and terminals should include being +available for beta testing as other changes are made. (Send a short +note to mg-support.) Currently, beta test copies of Mg are made +available via Internet ftp, so beta testers need access to the +Internet. (UUCP sites that are customers of uunet can get it via +them. Contact uunet!uunet-request for details.) If you can't reach +one of us via a computer network, I suppose you could send a change to +my snail mail address below on 5" os9 format disks or 9 track tape +(ANSI variable label or Prime magsav format), but this effectivly +rules you out as a potential beta tester. (Don't expect the disk or +tape back unless you inculude a SASE with sufficent postage.) I will +not be sending out copies on magnetic media, so please don't ask. If +you somehow got an incomplete or non-standard copy, (i.e. missing one +of the sys directories mentioned here as working) complain to who you +got it from not to me. + + Robert Larson + 309 S. Alexandria Ave. + Apt. 117 + Los Angeles, CA 90020 + +Alternatively, and under the same conditions, you can send either a 3" +AmigaDOS format disk or a 9 track tape (Unix tar format) to: + + Mike Meyer + P.O. Box 4730 + Berkeley, CA 94704 + diff --git a/usr.bin/mg/basic.c b/usr.bin/mg/basic.c new file mode 100644 index 00000000000..b35e1defeef --- /dev/null +++ b/usr.bin/mg/basic.c @@ -0,0 +1,444 @@ +/* + * 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" + +VOID setgoal(); + +/* + * Go to beginning of line. + */ +/*ARGSUSED*/ +gotobol(f, 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*/ +backchar(f, n) +register int n; +{ + register LINE *lp; + + if (n < 0) return forwchar(f, -n); + while (n--) { + if (curwp->w_doto == 0) { + if ((lp=lback(curwp->w_dotp)) == curbp->b_linep) { + if (!(f & FFRAND)) + ewprintf("Beginning of buffer"); + return (FALSE); + } + curwp->w_dotp = lp; + curwp->w_doto = llength(lp); + curwp->w_flag |= WFMOVE; + } else + curwp->w_doto--; + } + return TRUE; +} + +/* + * Go to end of line. + */ +/*ARGSUSED*/ +gotoeol(f, 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*/ +forwchar(f, n) +register 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_linep) { + curwp->w_dotp = lback(curwp->w_dotp); + if (!(f & FFRAND)) + ewprintf("End of buffer"); + return FALSE; + } + curwp->w_doto = 0; + curwp->w_flag |= WFMOVE; + } else + curwp->w_doto++; + } + return TRUE; +} + +/* + * Go to the beginning of the + * buffer. Setting WFHARD is conservative, + * but almost always the case. + */ +gotobob(f, n) +{ + (VOID) setmark(f, n) ; + curwp->w_dotp = lforw(curbp->b_linep); + curwp->w_doto = 0; + curwp->w_flag |= WFHARD; + return TRUE; +} + +/* + * Go to the end of the buffer. + * Setting WFHARD is conservative, but + * almost always the case. + */ +gotoeob(f, n) +{ + (VOID) setmark(f, n) ; + curwp->w_dotp = lback(curbp->b_linep); + curwp->w_doto = llength(curwp->w_dotp); + curwp->w_flag |= WFHARD; + 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*/ +forwline(f, n) +{ + register LINE *dlp; + + if (n < 0) + return backline(f|FFRAND, -n); + if ((lastflag&CFCPCN) == 0) /* Fix goal. */ + setgoal(); + thisflag |= CFCPCN; + if (n == 0) return TRUE; + dlp = curwp->w_dotp; + while (dlp!=curbp->b_linep && n--) + dlp = lforw(dlp); + curwp->w_flag |= WFMOVE; + if(dlp==curbp->b_linep) { /* ^N at end of buffer creates lines (like gnu) */ + if(!(curbp->b_flag&BFCHG)) { /* first change */ + curbp->b_flag |= BFCHG; + curwp->w_flag |= WFMODE; + } + curwp->w_doto = 0; + while(n-- >= 0) { + if((dlp = lallocx(0)) == NULL) return FALSE; + dlp->l_fp = curbp->b_linep; + dlp->l_bp = lback(dlp->l_fp); + dlp->l_bp->l_fp = dlp->l_fp->l_bp = dlp; + } + curwp->w_dotp = lback(curbp->b_linep); + } else { + 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*/ +backline(f, n) +{ + register 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_linep) + dlp = lback(dlp); + curwp->w_dotp = dlp; + curwp->w_doto = getgoal(dlp); + curwp->w_flag |= 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() { + + curgoal = getcolpos() - 1; /* 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. + */ +getgoal(dlp) register LINE *dlp; { + register int c; + register int col; + register int newcol; + register int dbo; + + col = 0; + dbo = 0; + while (dbo != llength(dlp)) { + c = lgetc(dlp, dbo); + newcol = col; + if (c == '\t' +#ifdef NOTAB + && !(curbp->b_flag & BFNOTAB) +#endif + ) + newcol |= 0x07; + else if (ISCTRL(c) != FALSE) + ++newcol; + ++newcol; + if (newcol > curgoal) + break; + col = newcol; + ++dbo; + } + return (dbo); +} + +/* + * 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*/ +forwpage(f, n) +register int n; +{ + register 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); +#ifdef CVMVAS + else /* Convert from pages */ + n *= curwp->w_ntrows; /* to lines. */ +#endif + lp = curwp->w_linep; + while (n-- && lforw(lp)!=curbp->b_linep) + lp = lforw(lp); + curwp->w_linep = lp; + curwp->w_flag |= WFHARD; + /* if in current window, don't move dot */ + for(n = curwp->w_ntrows; n-- && lp!=curbp->b_linep; lp = lforw(lp)) + if(lp==curwp->w_dotp) return TRUE; + curwp->w_dotp = curwp->w_linep; + 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*/ +backpage(f, n) +register int n; +{ + register LINE *lp; + + if (!(f & FFARG)) { + n = curwp->w_ntrows - 2; /* Default scroll. */ + if (n <= 0) /* Don't blow up if the */ + n = 1; /* window is tiny. */ + } else if (n < 0) + return forwpage(f|FFRAND, -n); +#ifdef CVMVAS + else /* Convert from pages */ + n *= curwp->w_ntrows; /* to lines. */ +#endif + lp = curwp->w_linep; + while (n-- && lback(lp)!=curbp->b_linep) + lp = lback(lp); + curwp->w_linep = lp; + curwp->w_flag |= WFHARD; + /* if in current window, don't move dot */ + for(n = curwp->w_ntrows; n-- && lp!=curbp->b_linep; lp = lforw(lp)) + if(lp==curwp->w_dotp) return TRUE; + curwp->w_dotp = curwp->w_linep; + 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. + */ + +#ifdef GOSMACS +forw1page(f, n) +int f, n; +{ + if (!(f & FFARG)) { + n = 1; + f = FFUNIV; + } + forwpage(f|FFRAND, n); +} + +back1page(f, n) +int f, n; +{ + if (!(f & FFARG)) { + n = 1; + f = FFUNIV; + } + backpage(f|FFRAND, n); +} +#endif + +/* + * Page the other window. Check to make sure it exists, then + * nextwind, forwpage and restore window pointers. + */ +pagenext(f, n) +{ + register WINDOW *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() +{ + curwp->w_markp = curwp->w_dotp; + curwp->w_marko = curwp->w_doto; +} + +/* + * 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*/ +setmark(f, n) +{ + isetmark(); + ewprintf("Mark set"); + 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*/ +swapmark(f, n) +{ + register LINE *odotp; + register int odoto; + + if (curwp->w_markp == NULL) { + ewprintf("No mark in this window"); + return FALSE; + } + odotp = curwp->w_dotp; + odoto = curwp->w_doto; + curwp->w_dotp = curwp->w_markp; + curwp->w_doto = curwp->w_marko; + curwp->w_markp = odotp; + curwp->w_marko = odoto; + curwp->w_flag |= 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*/ +gotoline(f, n) +register int n; +{ + register LINE *clp; + register int s; + char buf[32]; + + if (!(f & FFARG)) { + if ((s=ereply("Goto line: ", buf, sizeof(buf))) != TRUE) + return s; + n = atoi(buf); + } + + if (n > 0) { + clp = lforw(curbp->b_linep); /* "clp" is first line */ + while (--n > 0) { + if (lforw(clp) == curbp->b_linep) break; + clp = lforw(clp); + } + } else { + clp = lback(curbp->b_linep); /* clp is last line */ + while (n < 0) { + if (lback(clp) == curbp->b_linep) break; + clp = lback(clp); + n++; + } + } + curwp->w_dotp = clp; + curwp->w_doto = 0; + curwp->w_flag |= WFMOVE; + return TRUE; +} diff --git a/usr.bin/mg/buffer.c b/usr.bin/mg/buffer.c new file mode 100644 index 00000000000..b4244086bf7 --- /dev/null +++ b/usr.bin/mg/buffer.c @@ -0,0 +1,584 @@ +/* + * Buffer handling. + */ +#include "def.h" +#include "kbd.h" /* needed for modes */ + +static RSIZE itor(); + +/* + * 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*/ +usebuffer(f, n) +{ + register BUFFER *bp; + register int s; + char bufn[NBUFN]; + + /* Get buffer to use from user */ + if ((curbp->b_altb == NULL) + && ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL)) + s=eread("Switch to buffer: ", bufn, NBUFN, EFNEW|EFBUF); + else + s=eread("Switch to buffer: (default %s) ", bufn, NBUFN, + EFNEW|EFBUF, curbp->b_altb->b_bname); + + if (s == ABORT) return s; + if (s == FALSE && curbp->b_altb != NULL) bp = curbp->b_altb ; + else if ((bp=bfind(bufn, TRUE)) == NULL) return FALSE; + + /* and put it in current window */ + curbp = bp; + return showbuffer(bp, curwp, WFFORCE|WFHARD); +} + +/* + * pop to buffer asked for by the user. + */ +/*ARGSUSED*/ +poptobuffer(f, n) +{ + register BUFFER *bp; + register WINDOW *wp; + register int s; + char bufn[NBUFN]; + WINDOW *popbuf(); + + /* Get buffer to use from user */ + if ((curbp->b_altb == NULL) + && ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL)) + s=eread("Switch to buffer in other window: ", bufn, NBUFN, + EFNEW|EFBUF); + else + s=eread("Switch to buffer in other window: (default %s) ", + bufn, NBUFN, EFNEW|EFBUF, curbp->b_altb->b_bname); + if (s == ABORT) return s; + if (s == FALSE && curbp->b_altb != NULL) bp = curbp->b_altb ; + else if ((bp=bfind(bufn, TRUE)) == NULL) return FALSE; + + /* and put it in a new window */ + if ((wp = popbuf(bp)) == 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*/ +killbuffer(f, n) +{ + register BUFFER *bp; + register BUFFER *bp1; + register BUFFER *bp2; + WINDOW *wp; + register int s; + char bufn[NBUFN]; + + if ((s=eread("Kill buffer: (default %s) ", bufn, NBUFN, EFNEW|EFBUF, + curbp->b_bname)) == ABORT) return (s); + else if (s == FALSE) bp = curbp; + else if ((bp=bfind(bufn, FALSE)) == NULL) return FALSE; + + /* 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 FALSE; + /* create *scratch* for alternate buffer */ + if ((bp1 = bfind("*scratch*",TRUE)) == NULL) + return FALSE; + } + } + if (bclear(bp) != TRUE) return TRUE; + 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|WFFORCE|WFHARD) != NULL) + bp1->b_altb = bp2; + else bp1 = bp2; + } + } + if (bp == curbp) curbp = bp1; + free((char *) bp->b_linep); /* 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; + } + free(bp->b_bname); /* Release name block */ + free((char *) bp); /* Release buffer block */ + return TRUE; +} + +/* + * Save some buffers - just call anycb with the arg flag. + */ +/*ARGSUSED*/ +savebuffers(f, n) +{ + if (anycb(f) == ABORT) return ABORT; + return TRUE; +} + +/* + * 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*/ +listbuffers(f, n) +{ + register BUFFER *bp; + register WINDOW *wp; + BUFFER *makelist(); + WINDOW *popbuf(); + + if ((bp=makelist()) == NULL || (wp=popbuf(bp)) == NULL) + return FALSE; + wp->w_dotp = bp->b_dotp; /* fix up if window already on screen */ + wp->w_doto = bp->b_doto; + return TRUE; +} + +/* + * This routine rebuilds the text for the + * list buffers command. Return TRUE if + * everything works. Return FALSE if there + * is an error (if there is no memory). + */ +BUFFER * +makelist() { + register char *cp1; + register char *cp2; + register int c; + register BUFFER *bp; + LINE *lp; + register RSIZE nbytes; + BUFFER *blp; + char b[6+1]; + char line[128]; + + if ((blp = bfind("*Buffer List*", TRUE)) == NULL) return NULL; + if (bclear(blp) != TRUE) return NULL; + blp->b_flag &= ~BFCHG; /* Blow away old. */ + + (VOID) strcpy(line, " MR Buffer"); + cp1 = line + 10; + while(cp1 < line + 4 + NBUFN + 1) *cp1++ = ' '; + (VOID) strcpy(cp1, "Size File"); + if (addline(blp, line) == FALSE) return NULL; + (VOID) strcpy(line, " -- ------"); + cp1 = line + 10; + while(cp1 < line + 4 + NBUFN + 1) *cp1++ = ' '; + (VOID) strcpy(cp1, "---- ----"); + if (addline(blp, line) == FALSE) return NULL; + bp = bheadp; /* For all buffers */ + while (bp != NULL) { + cp1 = &line[0]; /* Start at left edge */ + *cp1++ = (bp == curbp) ? '.' : ' '; + *cp1++ = ((bp->b_flag&BFCHG) != 0) ? '*' : ' '; + *cp1++ = ' '; /* Gap. */ + *cp1++ = ' '; + cp2 = &bp->b_bname[0]; /* Buffer name */ + while ((c = *cp2++) != 0) + *cp1++ = c; + while (cp1 < &line[4+NBUFN+1]) + *cp1++ = ' '; + nbytes = 0; /* Count bytes in buf. */ + if (bp != blp) { + lp = lforw(bp->b_linep); + while (lp != bp->b_linep) { + nbytes += llength(lp)+1; + lp = lforw(lp); + } + if(nbytes) nbytes--; /* no bonus newline */ + } + (VOID) itor(b, 6, nbytes); /* 6 digit buffer size. */ + cp2 = &b[0]; + while ((c = *cp2++) != 0) + *cp1++ = c; + *cp1++ = ' '; /* Gap.. */ + cp2 = &bp->b_fname[0]; /* File name */ + if (*cp2 != 0) { + while ((c = *cp2++) != 0) { + if (cp1 < &line[128-1]) + *cp1++ = c; + } + } + *cp1 = 0; /* Add to the buffer. */ + if (addline(blp, line) == FALSE) + return NULL; + bp = bp->b_bufp; + } + blp->b_dotp = lforw(blp->b_linep); /* put dot at beginning of buffer */ + blp->b_doto = 0; + return blp; /* All done */ +} + +/* + * Used above. + */ +static RSIZE itor(buf, width, num) +register char buf[]; register int width; register RSIZE num; { + register RSIZE r; + + if (num / 10 == 0) { + buf[0] = (num % 10) + '0'; + for (r = 1; r < width; buf[r++] = ' ') + ; + buf[width] = '\0'; + return 1; + } else { + buf[r = itor(buf, width, num / (RSIZE)10)] = + (num % (RSIZE)10) + '0'; + return r + 1; + } + /*NOTREACHED*/ +} + +/* + * The argument "text" points to + * a 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. + */ +addline(bp, text) register BUFFER *bp; char *text; { + register LINE *lp; + register int i; + register int ntext; + + ntext = strlen(text); + if ((lp=lalloc(ntext)) == NULL) + return FALSE; + for (i=0; i<ntext; ++i) + lputc(lp, i, text[i]); + bp->b_linep->l_bp->l_fp = lp; /* Hook onto the end */ + lp->l_bp = bp->b_linep->l_bp; + bp->b_linep->l_bp = lp; + lp->l_fp = bp->b_linep; +#ifdef CANTHAPPEN + if (bp->b_dotp == bp->b_linep) /* If "." is at the end */ + bp->b_dotp = lp; /* move it to new line */ + if (bp->b_markp == bp->b_linep) /* ditto for mark */ + bp->b_markp = lp; +#endif + 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. + */ +anycb(f) { + register BUFFER *bp; + register int s = FALSE, save = FALSE; + char prompt[NFILEN + 11]; + VOID upmodes(); + + for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { + if (*(bp->b_fname) != '\0' + && (bp->b_flag&BFCHG) != 0) { + (VOID) strcpy(prompt, "Save file "); + (VOID) strcpy(prompt + 10, bp->b_fname); + if ((f == TRUE || (save = eyorn(prompt)) == 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 buffer and put it in the list of + * all buffers. Return pointer to the BUFFER + * block for the buffer. + */ +BUFFER * +bfind(bname, cflag) register char *bname; { + register BUFFER *bp; + char *malloc(); + register LINE *lp; + int i; + extern int defb_nmodes; + extern MAPS *defb_modes[PBMODES]; + extern int defb_flag; + + bp = bheadp; + while (bp != NULL) { + if (fncmp(bname, bp->b_bname) == 0) + return bp; + bp = bp->b_bufp; + } + if (cflag!=TRUE) return NULL; + /*NOSTRICT*/ + if ((bp=(BUFFER *)malloc(sizeof(BUFFER))) == NULL) { + ewprintf("Can't get %d bytes", sizeof(BUFFER)); + return NULL; + } + if ((bp->b_bname=malloc((unsigned)(strlen(bname)+1))) == NULL) { + ewprintf("Can't get %d bytes", strlen(bname)+1); + free((char *) bp); + return NULL; + } + if ((lp = lalloc(0)) == NULL) { + free(bp->b_bname); + free((char *) 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; + bp->b_nwnd = 0; + bp->b_linep = lp; + bp->b_nmodes = defb_nmodes; + i = 0; + do { + bp->b_modes[i] = defb_modes[i]; + } while(i++ < defb_nmodes); + bp->b_fname[0] = '\0'; + bzero(&bp->b_fi, sizeof(bp->b_fi)); + (VOID) strcpy(bp->b_bname, bname); + lp->l_fp = lp; + lp->l_bp = lp; + bp->b_bufp = bheadp; + bheadp = bp; + 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. + */ +bclear(bp) register BUFFER *bp; { + register LINE *lp; + register int s; + VOID lfree(); + + if ((bp->b_flag&BFCHG) != 0 /* Changed. */ + && (s=eyesno("Buffer modified; kill anyway")) != TRUE) + return (s); + bp->b_flag &= ~BFCHG; /* Not changed */ + while ((lp=lforw(bp->b_linep)) != bp->b_linep) + lfree(lp); + bp->b_dotp = bp->b_linep; /* Fix "." */ + bp->b_doto = 0; + bp->b_markp = NULL; /* Invalidate "mark" */ + bp->b_marko = 0; + return TRUE; +} + +/* + * Display the given buffer in the given window. Flags indicated + * action on redisplay. + */ +showbuffer(bp, wp, flags) register BUFFER *bp; register WINDOW *wp; { + register BUFFER *obp; + WINDOW *owp; + + if (wp->w_bufp == bp) { /* Easy case! */ + wp->w_flag |= flags; + return TRUE; + } + + /* First, dettach 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; + } + } + + /* 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; + } 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; + break; + } + wp->w_flag |= WFMODE|flags; + return TRUE; +} + +/* + * Pop the buffer we got passed onto the screen. + * Returns a status. + */ +WINDOW * +popbuf(bp) register BUFFER *bp; { + register WINDOW *wp; + + if (bp->b_nwnd == 0) { /* Not on screen yet. */ + if ((wp=wpopup()) == NULL) return NULL; + } else + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) + if (wp->w_bufp == bp) { + wp->w_flag |= WFHARD|WFFORCE; + return wp ; + } + if (showbuffer(bp, wp, WFHARD) != TRUE) return NULL; + return wp; +} + +/* + * Insert another buffer at dot. Very useful. + */ +/*ARGSUSED*/ +bufferinsert(f, n) +{ + register BUFFER *bp; + register LINE *clp; + register int clo; + register int nline; + int s; + char bufn[NBUFN]; + + /* Get buffer to use from user */ + if (curbp->b_altb != NULL) + s=eread("Insert buffer: (default %s) ", bufn, NBUFN, + EFNEW|EFBUF, &(curbp->b_altb->b_bname), + (char *) NULL) ; + else + s=eread("Insert buffer: ", bufn, NBUFN, EFNEW|EFBUF, + (char *) NULL) ; + if (s == ABORT) return (s); + if (s == FALSE && 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 = lforw(bp->b_linep); + for(;;) { + for (clo = 0; clo < llength(clp); clo++) + if (linsert(1, lgetc(clp, clo)) == FALSE) + return FALSE; + if((clp = lforw(clp)) == bp->b_linep) 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_linep) + clp = lback(clp); + curwp->w_linep = clp; /* adjust framing. */ + curwp->w_flag |= WFHARD; + } + return (TRUE); +} + +/* + * Turn off the dirty bit on this buffer. + */ +/*ARGSUSED*/ +notmodified(f, n) +{ + register WINDOW *wp; + + curbp->b_flag &= ~BFCHG; + wp = wheadp; /* Update mode lines. */ + while (wp != NULL) { + if (wp->w_bufp == curbp) + wp->w_flag |= WFMODE; + wp = wp->w_wndp; + } + ewprintf("Modification-flag cleared"); + return TRUE; +} + +#ifndef NO_HELP +/* + * Popbuf and set all windows to top of buffer. Currently only used by + * help functions. + */ + +popbuftop(bp) +register BUFFER *bp; +{ + register WINDOW *wp; + + bp->b_dotp = lforw(bp->b_linep); + 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_flag |= WFHARD; + } + } + return popbuf(bp) != NULL; +} +#endif diff --git a/usr.bin/mg/chrdef.h b/usr.bin/mg/chrdef.h new file mode 100644 index 00000000000..6c2f2f3180f --- /dev/null +++ b/usr.bin/mg/chrdef.h @@ -0,0 +1,79 @@ +/* + * sys/default/chardef.h: character set specific #defines for mg 2a + * Warning: System specific ones exist + */ + +#ifndef CHARMASK +/* + * casting should be at least as efficent 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)) +#endif + +/* + * 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 _W 0x01 /* Word. */ +#define _U 0x02 /* Upper case letter. */ +#define _L 0x04 /* Lower case letter. */ +#define _C 0x08 /* Control. */ +#define _P 0x10 /* end of sentence punctuation */ +#define _D 0x20 /* is decimal digit */ + +#define ISWORD(c) ((cinfo[CHARMASK(c)]&_W)!=0) +#define ISCTRL(c) ((cinfo[CHARMASK(c)]&_C)!=0) +#define ISUPPER(c) ((cinfo[CHARMASK(c)]&_U)!=0) +#define ISLOWER(c) ((cinfo[CHARMASK(c)]&_L)!=0) +#define ISEOSP(c) ((cinfo[CHARMASK(c)]&_P)!=0) +#define ISDIGIT(c) ((cinfo[CHARMASK(c)]&_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/usr.bin/mg/cinfo.c b/usr.bin/mg/cinfo.c new file mode 100644 index 00000000000..163017e0e07 --- /dev/null +++ b/usr.bin/mg/cinfo.c @@ -0,0 +1,135 @@ +/* + * 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. + */ +char cinfo[256] = { + _C, _C, _C, _C, /* 0x0X */ + _C, _C, _C, _C, + _C, _C, _C, _C, + _C, _C, _C, _C, + _C, _C, _C, _C, /* 0x1X */ + _C, _C, _C, _C, + _C, _C, _C, _C, + _C, _C, _C, _C, + 0, _P, 0, 0, /* 0x2X */ + _W, _W, 0, _W, + 0, 0, 0, 0, + 0, 0, _P, 0, + _D|_W, _D|_W, _D|_W, _D|_W, /* 0x3X */ + _D|_W, _D|_W, _D|_W, _D|_W, + _D|_W, _D|_W, 0, 0, + 0, 0, 0, _P, + 0, _U|_W, _U|_W, _U|_W, /* 0x4X */ + _U|_W, _U|_W, _U|_W, _U|_W, + _U|_W, _U|_W, _U|_W, _U|_W, + _U|_W, _U|_W, _U|_W, _U|_W, + _U|_W, _U|_W, _U|_W, _U|_W, /* 0x5X */ + _U|_W, _U|_W, _U|_W, _U|_W, + _U|_W, _U|_W, _U|_W, 0, + 0, 0, 0, 0, + 0, _L|_W, _L|_W, _L|_W, /* 0x6X */ + _L|_W, _L|_W, _L|_W, _L|_W, + _L|_W, _L|_W, _L|_W, _L|_W, + _L|_W, _L|_W, _L|_W, _L|_W, + _L|_W, _L|_W, _L|_W, _L|_W, /* 0x7X */ + _L|_W, _L|_W, _L|_W, _L|_W, + _L|_W, _L|_W, _L|_W, 0, + 0, 0, 0, _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, + _U|_W, _U|_W, _U|_W, _U|_W, /* 0xCX */ + _U|_W, _U|_W, _U|_W, _U|_W, + _U|_W, _U|_W, _U|_W, _U|_W, + _U|_W, _U|_W, _U|_W, _U|_W, + 0, _U|_W, _U|_W, _U|_W, /* 0xDX */ + _U|_W, _U|_W, _U|_W, _U|_W, + _U|_W, _U|_W, _U|_W, _U|_W, + _U|_W, _U|_W, 0, _W, + _L|_W, _L|_W, _L|_W, _L|_W, /* 0xEX */ + _L|_W, _L|_W, _L|_W, _L|_W, + _L|_W, _L|_W, _L|_W, _L|_W, + _L|_W, _L|_W, _L|_W, _L|_W, + 0, _L|_W, _L|_W, _L|_W, /* 0xFX */ + _L|_W, _L|_W, _L|_W, _L|_W, + _L|_W, _L|_W, _L|_W, _L|_W, + _L|_W, _L|_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'. + */ + +char *keyname(cp, k) +register char *cp; +register int k; +{ + register char *np; +#ifdef FKEYS + extern char *keystrings[]; +#endif + + if(k < 0) k = CHARMASK(k); /* sign extended char */ + switch(k) { + case CCHR('@'): np = "NUL"; break; + case CCHR('I'): np = "TAB"; break; + case CCHR('J'): np = "LFD"; break; /* yuck, but that's what GNU calls it */ + 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; + } + if(k < ' ') { + *cp++ = 'C'; + *cp++ = '-'; + k = CCHR(k); + if(ISUPPER(k)) k = TOLOWER(k); + } + *cp++ = k; + *cp = '\0'; + return cp; + } + (VOID) strcpy(cp, np); + return cp + strlen(cp); +} diff --git a/usr.bin/mg/def.h b/usr.bin/mg/def.h new file mode 100644 index 00000000000..a54956cab83 --- /dev/null +++ b/usr.bin/mg/def.h @@ -0,0 +1,312 @@ +/* + * This file is the general header file for all parts + * of the MicroEMACS 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. + * The most common reason to edit this file would be to zap + * the definition of CVMVAS or BACKUP. + */ +#include "sysdef.h" /* Order is critical. */ +#include "ttydef.h" +#include "chrdef.h" + +/* + * If your system and/or compiler does not support the "void" type + * then define NO_VOID_TYPE in sysdef.h. In the absence of some + * other definition for VOID, the default in that case will be to + * turn it into an int, which works with most compilers that don't + * support void. In the absence of any definition of VOID or + * NO_VOID_TYPE, the default is to assume void is supported, which + * should be the case for most modern C compilers. + */ + +#ifdef NO_VOID_TYPE +# undef VOID +# define VOID int /* Default for no void is int */ +#else +#ifndef VOID +# define VOID void /* Just use normal void */ +#endif /* VOID */ +#endif /* NO_VOID_TYPE */ + +#ifdef NO_MACRO +#ifndef NO_STARTUP +#define NO_STARTUP /* NO_MACRO implies NO_STARTUP */ +#endif +#endif + +typedef int (*PF)(); /* generaly useful type */ + +/* + * Table sizes, etc. + */ +#define NFILEN 80 /* Length, file name. */ +#define NBUFN 24 /* 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 */ +/* + * Universal. + */ +#define FALSE 0 /* False, no, bad, etc. */ +#define TRUE 1 /* True, yes, good, etc. */ +#define ABORT 2 /* Death, ^G, abort, etc. */ + +#define KPROMPT 2 /* keyboard prompt */ + +/* + * 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 */ + +/* + * 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 involked functions */ + +#define FFUNIV 1 /* universal argument */ +#define FFNEGARG 2 /* negitive 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 autocompleteion on */ +#define EFNEW 0x0008 /* New prompt. */ +#define EFCR 0x0010 /* Echo CR at end; last read. */ + +/* + * Flags for "ldelete"/"kinsert" + */ + +#define KNONE 0 +#define KFORW 1 +#define KBACK 2 + +/* + * 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". Each line contains a 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. + */ +typedef 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 */ +#ifndef ZEROARRAY + char l_text[1]; /* A bunch of characters. */ +#else + char l_text[]; /* A bunch of characters. */ +#endif +} LINE; + +/* + * The rationale behind these macros is that you + * could (with some editing, like changing the type of a line + * link from a "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. + */ +typedef struct LIST { + union { + struct WINDOW *l_wp; + struct BUFFER *x_bp; /* l_bp is used by LINE */ + struct LIST *l_nxt; + } l_p; + char *l_name; +} LIST; +/* + * 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. + */ +typedef struct WINDOW { + 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" */ + char w_toprow; /* Origin 0 top row of window */ + char w_ntrows; /* # of rows of text in window */ + char w_force; /* If NZ, forcing row. */ + char w_flag; /* Flags. */ +} WINDOW; +#define w_wndp w_list.l_p.l_wp +#define w_name w_list.l_name + +/* + * Window flags are set by command processors to + * tell the display system what has happened to the buffer + * mapped by the window. Setting "WFHARD" 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 WFFORCE 0x01 /* Force reframe. */ +#define WFMOVE 0x02 /* Movement from line to line. */ +#define WFEDIT 0x04 /* Editing within a line. */ +#define WFHARD 0x08 /* Better to a full display. */ +#define WFMODE 0x10 /* Update mode line. */ + +/* + * 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_linep". + */ +typedef struct BUFFER { + 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_linep; /* 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 */ + struct fileinfo b_fi; /* File attributes */ +} BUFFER; +#define b_bufp b_list.l_p.x_bp +#define b_bname b_list.l_name + +#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 */ + +/* + * 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. + */ +typedef struct { + struct LINE *r_linep; /* Origin LINE address. */ + int r_offset; /* Origin LINE offset. */ + RSIZE r_size; /* Length in characters. */ +} REGION; + +/* + * Externals. + */ +extern int thisflag; +extern int lastflag; +extern int curgoal; +extern int epresf; +extern int sgarbf; +extern int mode; +extern WINDOW *curwp; +extern BUFFER *curbp; +extern WINDOW *wheadp; +extern BUFFER *bheadp; +extern char pat[]; +extern BUFFER *bfind(); +extern WINDOW *popbuf(); +extern WINDOW *wpopup(); +extern LINE *lalloc(); +extern LINE *lallocx(); +extern VOID ewprintf(); +extern int nrow; +extern int ncol; +extern int ttrow; +extern int ttcol; +extern int tceeol; +extern int tcinsl; +extern int tcdell; +extern char cinfo[]; +extern char *keystrings[]; +extern VOID update(); +extern char *keyname(); +extern char *adjustname(); +extern VOID kdelete(); +extern VOID lchange(); +/* + * Standard I/O. + */ +extern char *strcpy(); +extern char *strcat(); +extern char *malloc(); diff --git a/usr.bin/mg/dir.c b/usr.bin/mg/dir.c new file mode 100644 index 00000000000..b8f40f973ee --- /dev/null +++ b/usr.bin/mg/dir.c @@ -0,0 +1,59 @@ +/* + * 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" + +#ifndef NO_DIR +#ifndef getwd /* may be a #define */ +char *getwd(); +#endif +char *wdir; +static char cwd[NFILEN]; + +/* + * Initialize anything the directory management routines need + */ +dirinit() +{ + if (!(wdir = getwd(cwd))) + panic("Can't get current directory!"); +} + +/* + * Change current working directory + */ +/*ARGSUSED*/ +changedir(f, n) +{ + register int s; + char bufc[NPAT]; + + if ((s=ereply("Change default directory: ", bufc, NPAT)) != TRUE) + return(s); + if (bufc[0] == '\0') + (VOID) strcpy(bufc, wdir); + if (chdir(bufc) == -1) { + ewprintf("Can't change dir to %s", bufc); + return(FALSE); + } else { + if (!(wdir = getwd(cwd))) + panic("Can't get current directory!"); + ewprintf("Current directory is now %s", wdir); + return(TRUE); + } +} + +/* + * Show current directory + */ +/*ARGSUSED*/ +showcwdir(f, n) +{ + ewprintf("Current directory: %s", wdir); + return(TRUE); +} +#endif diff --git a/usr.bin/mg/dired.c b/usr.bin/mg/dired.c new file mode 100644 index 00000000000..2d3aedbbb4e --- /dev/null +++ b/usr.bin/mg/dired.c @@ -0,0 +1,195 @@ +/* dired module for mg 2a */ +/* by Robert A. Larson */ + +#include "def.h" + +#ifndef NO_DIRED + +BUFFER *dired_(); + +/*ARGSUSED*/ +dired(f, n) +int f, n; +{ + char dirname[NFILEN]; + BUFFER *bp; + + dirname[0] = '\0'; + if(eread("Dired: ", dirname, NFILEN, EFNEW | EFCR) == ABORT) + return ABORT; + if((bp = dired_(dirname)) == NULL) return FALSE; + curbp = bp; + return showbuffer(bp, curwp, WFHARD | WFMODE); +} + +/*ARGSUSED*/ +d_otherwindow(f, n) +int f, n; +{ + char dirname[NFILEN]; + BUFFER *bp; + WINDOW *wp; + + dirname[0] = '\0'; + if(eread("Dired other window: ", dirname, NFILEN, EFNEW | EFCR) == ABORT) + return ABORT; + if((bp = dired_(dirname)) == NULL) return FALSE; + if((wp = popbuf(bp)) == NULL) return FALSE; + curbp = bp; + curwp = wp; + return TRUE; +} + +/*ARGSUSED*/ +d_del(f, n) +int f, 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_linep) + curwp->w_dotp = lforw(curwp->w_dotp); + } + curwp->w_flag |= WFEDIT | WFMOVE; + curwp->w_doto = 0; + return TRUE; +} + +/*ARGSUSED*/ +d_undel(f, n) +int f, 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_linep) + curwp->w_dotp = lforw(curwp->w_dotp); + } + curwp->w_flag |= WFEDIT | WFMOVE; + curwp->w_doto = 0; + return TRUE; +} + +/*ARGSUSED*/ +d_undelbak(f, n) +int f, 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_linep) + curwp->w_dotp = lback(curwp->w_dotp); + } + curwp->w_doto = 0; + curwp->w_flag |= WFEDIT | WFMOVE; + return TRUE; +} + +/*ARGSUSED*/ +d_findfile(f, n) +int f, n; +{ + char fname[NFILEN]; + register BUFFER *bp; + register int s; + BUFFER *findbuffer(); + + if((s = d_makename(curwp->w_dotp, fname)) == ABORT) return FALSE; + if ((bp = (s ? dired_(fname) : findbuffer(fname))) == NULL) return FALSE; + curbp = bp; + if (showbuffer(bp, curwp, WFHARD) != TRUE) return FALSE; + if (bp->b_fname[0] != 0) return TRUE; + return readin(fname); +} + +/*ARGSUSED*/ +d_ffotherwindow(f, n) +int f, n; +{ + char fname[NFILEN]; + register BUFFER *bp; + register int s; + register WINDOW *wp; + BUFFER *findbuffer(); + + if((s = d_makename(curwp->w_dotp, fname)) == ABORT) return FALSE; + if ((bp = (s ? dired_(fname) : findbuffer(fname))) == NULL) return FALSE; + if ((wp = popbuf(bp)) == NULL) return FALSE; + curbp = bp; + curwp = wp; + if (bp->b_fname[0] != 0) return TRUE; /* never true for dired buffers */ + return readin(fname); +} + +/*ARGSUSED*/ +d_expunge(f, n) +int f, n; +{ + register LINE *lp, *nlp; + char fname[NFILEN]; + VOID lfree(); + + for(lp = lforw(curbp->b_linep); lp != curbp->b_linep; lp = nlp) { + nlp = lforw(lp); + if(llength(lp) && lgetc(lp, 0) == 'D') { + switch(d_makename(lp, fname)) { + case ABORT: + ewprintf("Bad line in dired buffer"); + return FALSE; + case FALSE: + if(unlink(fname) < 0) { + ewprintf("Could not delete '%s'", fname); + return FALSE; + } + break; + case TRUE: + if(unlinkdir(fname) < 0) { + ewprintf("Could not delete directory '%s'", fname); + return FALSE; + } + break; + } + lfree(lp); + curwp->w_flag |= WFHARD; + } + } + return TRUE; +} + +/*ARGSUSED*/ +d_copy(f, n) +int f, n; +{ + char frname[NFILEN], toname[NFILEN]; + int stat; + + if(d_makename(curwp->w_dotp, frname) != FALSE) { + ewprintf("Not a file"); + return FALSE; + } + if((stat = eread("Copy %s to: ", toname, NFILEN, EFNEW | EFCR, frname)) + != TRUE) return stat; + return copy(frname, toname) >= 0; +} + +/*ARGSUSED*/ +d_rename(f, n) +int f, n; +{ + char frname[NFILEN], toname[NFILEN]; + int stat; + + if(d_makename(curwp->w_dotp, frname) != FALSE) { + ewprintf("Not a file"); + return FALSE; + } + if((stat = eread("Rename %s to: ", toname, NFILEN, EFNEW | EFCR, frname)) + != TRUE) return stat; + return Xrename(frname, toname) >= 0; +} +#endif + + diff --git a/usr.bin/mg/display.c b/usr.bin/mg/display.c new file mode 100644 index 00000000000..40fd1935a19 --- /dev/null +++ b/usr.bin/mg/display.c @@ -0,0 +1,884 @@ +/* + * 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. Two conditional compilation flags; the GOSLING + * flag enables dynamic programming redisplay, using the + * algorithm published by Jim Gosling in SIGOA. The MEMMAP + * changes things around for memory mapped video. With + * both off, the terminal is a VT52. + */ +#include "def.h" +#include "kbd.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. + */ +/* These defines really belong in sysdef.h */ +#ifndef XCHAR +# define XCHAR int +# define XSHORT int +#endif + +#ifdef STANDOUT_GLITCH +extern int SG; /* number of standout glitches */ +#endif + +/* + * A video structure always holds + * an array of characters whose length is equal to + * the longest line possible. Only some of this is + * used if "ncol" isn't the same as "NCOL". + */ +typedef struct { + 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[NCOL]; /* The actual characters. */ +} VIDEO; + +#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 score a "short", but + * this makes the code worse on the VAX. + */ +typedef struct { + XCHAR s_itrace; /* "i" index for track back. */ + XCHAR s_jtrace; /* "j" index for trace back. */ + XSHORT s_cost; /* Display cost. */ +} SCORE; + +int sgarbf = TRUE; /* TRUE if screen is garbage. */ +int vtrow = 0; /* Virtual cursor row. */ +int vtcol = 0; /* 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 */ + +VIDEO *vscreen[NROW-1]; /* Edge vector, virtual. */ +VIDEO *pscreen[NROW-1]; /* Edge vector, physical. */ +VIDEO video[2*(NROW-1)]; /* Actual screen data. */ +VIDEO blanks; /* Blank line image. */ + +/* + * Some predeclerations to make ANSI compilers happy + */ +VOID vtinit(); +VOID vttidy(); +VOID vtmove(); +VOID vtputc(); +VOID vtpute(); +VOID vteeol(); +VOID update(); +VOID updext(); +VOID ucopy(); +VOID uline(); +VOID modeline(); +VOID hash(); +VOID setscores(); +VOID traceback(); + +#ifdef GOSLING +/* + * 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. + */ +SCORE score[NROW*NROW]; +#endif + +/* + * 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() { + register VIDEO *vp; + register int i; + + ttopen(); + ttinit(); + vp = &video[0]; + for (i=0; i<NROW-1; ++i) { + vscreen[i] = vp; + ++vp; + pscreen[i] = vp; + ++vp; + } + 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() { + 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(row, 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(c) register int c; { + register VIDEO *vp; + + 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 + vp->v_text[vtcol++] = c; +} + +/* 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(c) +int c; +{ + register VIDEO *vp; + + 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() { + register 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() { + register LINE *lp; + register WINDOW *wp; + register VIDEO *vp1; + VIDEO *vp2; + register int i; + register int j; + register int c; + register int hflag; + register int currow; + register int curcol; + register int offs; + register int size; + VOID traceback (); + VOID uline (); + + if (typeahead()) return; + if (sgarbf) { /* must update everything */ + wp = wheadp; + while(wp != NULL) { + wp->w_flag |= WFMODE | WFHARD; + wp = wp->w_wndp; + } + } + hflag = FALSE; /* Not hard. */ + wp = wheadp; + while (wp != NULL) { + if (wp->w_flag != 0) { /* Need update. */ + if ((wp->w_flag&WFFORCE) == 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_linep) + break; + lp = lforw(lp); + } + } + i = wp->w_force; /* Reframe this one. */ + 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; + lp = wp->w_dotp; + while (i!=0 && lback(lp)!=wp->w_bufp->b_linep) { + --i; + lp = lback(lp); + } + wp->w_linep = lp; + wp->w_flag |= WFHARD; /* Force full. */ + out: + lp = wp->w_linep; /* Try reduced update. */ + i = wp->w_toprow; + if ((wp->w_flag&~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_flag&(WFEDIT|WFHARD)) != 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_linep) { + for (j=0; j<llength(lp); ++j) + vtputc(lgetc(lp, j)); + lp = lforw(lp); + } + vteeol(); + ++i; + } + } + if ((wp->w_flag&WFMODE) != 0) + modeline(wp); + wp->w_flag = 0; + wp->w_force = 0; + } + wp = wp->w_wndp; + } + 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; + else if (ISCTRL(c) != FALSE) + ++curcol; + ++curcol; + } + 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; + } +#ifdef GOSLING + 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; + } +#endif + 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(vvp, pvp) register VIDEO *vvp; register 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(currow, curcol) +int currow, curcol; +{ + register LINE *lp; /* pointer to current line */ + register int j; /* index into line */ + + /* 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(row, vvp, pvp) VIDEO *vvp; VIDEO *pvp; { +#ifdef MEMMAP + putline(row+1, 1, &vvp->v_text[0]); +#else + register char *cp1; + register char *cp2; + register char *cp3; + char *cp4; + char *cp5; + register 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 && SG >= 0) tteeol(); +#endif + ttcolor(vvp->v_color); +#ifdef STANDOUT_GLITCH + cp1 = &vvp->v_text[SG > 0 ? SG : 0]; + /* the odd code for SG==0 is to avoid putting the invisable + * glitch character on the next line. + * (Hazeltine executive 80 model 30) + */ + cp2 = &vvp->v_text[ncol - (SG >= 0 ? (SG!=0 ? SG : 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 && SG > 0) { + if(cp1 < &vvp->v_text[SG]) cp1 = &vvp->v_text[SG]; + if(cp5 > &vvp->v_text[ncol-SG]) cp5 = &vvp->v_text[ncol-SG]; + } else if (SG < 0) +#endif + ttcolor(vvp->v_color); + while (cp1 != cp5) { + ttputc(*cp1++); + ++ttcol; + } + if (cp5 != cp3) /* Do erase. */ + tteeol(); +#endif +} + +/* + * Redisplay the mode line for + * the window pointed to by the "wp". + * This is the only routine that has any idea + * of how the modeline 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 SG characters + * may never be seen. + */ +VOID +modeline(wp) register WINDOW *wp; { + register int n; + register BUFFER *bp; + int mode; + + 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&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(mode=0;;) { + n += vtputs(bp->b_modes[mode]->p_name); + if(++mode > bp->b_nmodes) break; + vtputc('-'); + ++n; + } + vtputc(')'); + ++n; + while (n < ncol) { /* Pad out. */ + vtputc('-'); + ++n; + } +} +/* + * output a string to the mode line, report how long it was. + */ +vtputs(s) register char *s; { + register int n = 0; + + while (*s != '\0') { + vtputc(*s++); + ++n; + } + return n; +} +#ifdef GOSLING +/* + * 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(vp) register VIDEO *vp; { + register int i; + register int n; + register 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(offs, size) { + register SCORE *sp; + SCORE *sp1; + register int tempcost; + register int bestcost; + register int j; + register int i; + register VIDEO **vp; + VIDEO **pp, **vbase, **pbase; + + 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(offs, size, i, j) { + register int itrace; + register int jtrace; + register int k; + register int ninsl; + register int ndraw; + register int 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]); +} +#endif diff --git a/usr.bin/mg/dvidoc.sty b/usr.bin/mg/dvidoc.sty new file mode 100644 index 00000000000..9dbb9cb8937 --- /dev/null +++ b/usr.bin/mg/dvidoc.sty @@ -0,0 +1,181 @@ +% dvidoc style file. Fixes things up so your file uses only fixed width +% non-math characters, at least if you don't use math mode +% +% The first section defines all the font change commands to use the +% doc pseudo-font. It is taken from John Pavel's dvidoc.sty, March 1987 +% +% version from John Pavel's dvidoc.sty, March 1987 +\def\rm{\protect\pdoc} +\def\it{\protect\pdoc} +\def\bf{\protect\pdoc} +\def\sl{\protect\pdoc} +\def\sf{\protect\pdoc} +\def\sc{\protect\pdoc} +\def\tt{\protect\pdoc} +\newfam\docfam +\def\pdoc{\@getfont\pdoc\docfam\@xpt{doc}} +% This gets all normal text, headings, etc. +% Unfortunately it doesn't catch places where more explicit stuff +% is done. The following is brute force but effective. Note that +% we leave the symbol fonts alone, since otherwise we'll get TeX +% errors complaining that it couldn't find symbol fonts +% Possibly we should do this for all of the font types. This caught +% everything in sample.tex. +\font\fivmi = doc +\font\fivrm = doc +\font\fivsy = cmsy10 +\font\sixmi = doc +\font\sixrm = doc +\font\sixsy = cmsy10 +\font\sevmi = doc +\font\sevrm = doc +\font\sevsy = cmsy10 +\font\egtmi = doc +\font\egtrm = doc +\font\egtsy = cmsy10 +\font\ninmi = doc +\font\ninrm = doc +\font\ninsy = cmsy10 +\font\tenmi = doc +\font\tenrm = doc +\font\elvmi = doc +\font\elvrm = doc +\font\elvsy = cmsy10 +\font\twlmi = doc +\font\twlsy = cmsy10 +\font\frtnmi = doc +\font\frtnrm = doc +\font\frtnsy = cmsy10 +\font\svtnmi = doc +\font\svtnrm = doc +\font\svtnsy = cmsy10 +\font\twtymi = doc +\font\twtyrm = doc +\font\twtysy = cmsy10 +\rm +% +% These dimensional definitions are also taken from Langdon, though with +% some changes, e.g. not ragged right/bottom and parind 3 instead of 5. +% horizontal dimensions had best be multiples of \em +\hsize 78 em % 78 characters per line so fit any screen +% \rightskip=0pt plus 4em % ragged right +% \spaceskip=1em % forces ONE space between words +% \frenchspacing suppresses extra blanks after punctuation -don't +\parindent=3em +\def\enspace{\kern 1em} \def\enskip{\hskip 1em\relax} +% +% vertical skips may best be multiples of \baselineskip +\baselineskip=12pt % 6 lines per inch +% \vsize % default give 58 lines -OK +\voffset=\baselineskip % so don't lose \headline +\parskip=0pt +\smallskipamount=0pt +\medskipamount= \baselineskip +\bigskipamount=2\baselineskip +% \raggedbottom +% +% By default itemize is done with bullets, which we don't have. use hyphen +% now fix up various latexish stuff +% +\def\labelitemi{ -} +\def\labelitemii{ -} +\def\labelitemiii{ -} +\def\labelitemiv{ -} +% +% Fix up table of contents. +% +% Dottedtocline has to be redefined because +% the original used math mode for the leader. Dots in math mode turn out +% to be colons. +% +% We've also got problems with spacing. The spacing for section number +% and page number isn't enough. Add 1em * level number in dottedtocline +% for the section number. Unfortunately, chapter and part are hardcoded in +% the style files and don't use dottedtocline. So we add 1em to +% numberline. Note that section and below get both the numberline and +% the dottedtocline adjustment. +\def\@dottedtocline#1#2#3#4#5{\ifnum #1>\c@tocdepth \else + \vskip \z@ plus .2pt + {\hangindent #2\relax \rightskip \@tocrmarg \parfillskip -\rightskip + \parindent #2\relax\@afterindenttrue + \interlinepenalty\@M + \leavevmode + \@tempdima 1em + \multiply \@tempdima #1 + \addtolength\@tempdima{#3} + #4\nobreak\leaders\hbox to 2em{\hss.\hss}\hfill \nobreak \hbox to\@pnumwidth{\hfil\rm #5}\par}\fi} +\def\numberline#1{\addtolength\@tempdima{2em} \hbox to\@tempdima{#1\hfil}} +% page number width and table of contents right margin +\def\@pnumwidth{3em} +\def\@tocrmarg {4em} +% +% dotfill also used math mode for the leaders. +% +\def\dotfill{\cleaders\hbox{\hss.\hss}\hfill\kern\z@} +% +% Can't really do superscripts, so do footnotes with [] +% +\def\@makefnmark{\hbox{/\@thefnmark/}} +\long\def\@makefntext#1{\parindent 1em\noindent + \hbox to 3em{\hss\@thefnmark.}\ #1} +\skip\footins 24pt plus 4pt minus 2pt +\def\footnoterule{\kern-12\p@ +\hbox to .4\columnwidth{\leaders\hbox{-}\hfill}} +% +% \arrayrulewidth 1em \doublerulesep 1em +% +% Some fairly obvious hacks. No odd/even pages in doc files. Can't do the +% fancy TeX symbols. +% +\oddsidemargin 0pt \evensidemargin 0pt +\def\TeX{TeX} +\def\LaTeX{LaTeX} +\def\SliTeX{SliTeX} +\def\BibTeX{BibTeX} +\def\copyright{(C)} +% +% special versions of stuff from xxx10.sty, since only one font size +% +\def\@normalsize{\@setsize\normalsize{12pt}\xpt\@xpt +\abovedisplayskip 12pt +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ +\belowdisplayshortskip \z@ +\let\@listi\@listI} % Setting of \@listi added 9 Jun 87 +\let\small\@normalsize +\let\footnotesize\@normalsize +\normalsize +\footnotesep 12pt +\labelsep 10pt +\def\@listI{\leftmargin\leftmargini \parsep 0pt% +\topsep 12pt% +\itemsep 12pt} +\let\@listi\@listI +\let\@listii\@listI +\let\@listiii\@listI +\let\@listiv\@listI +\let\@listv\@listI +\let\@listvi\@listI +\@listI +% +% We had sort of random numbers of blank lines around section numbers. +% Turns out they used various fractional spacing. Rather than depend +% upon the definition of startsection, just wrap something around it +% that normalizes the arguments to 12pt. Negative args have special +% meanings. Alas, this trick doesn't fix up chapter and part entries, +% as they have hardcoded extra vertical space. No way to do that +% without special definitions for l@chapter, which would be different +% for different styles. We're gonna get extra lines. Sorry. +\let\@oldstartsec\@startsection +\def\@startsection#1#2#3#4#5#6{ + \@tempskipa #4\relax + \@tempskipb #5\relax + \ifdim \@tempskipa <\z@ \@tempskipa -12pt \else \@tempskipa 12pt \fi + \ifdim \@tempskipb >\z@ \@tempskipb 12pt \fi +\@oldstartsec{#1}{#2}{#3}{\@tempskipa}{\@tempskipb}{#6} +} +% +% Remaining unsolved problem: hrule and vrule are no-ops. This is +% visible mostly in the tabular stuff, where none of the lines show up, +% though it's still basically usable. +% diff --git a/usr.bin/mg/echo.c b/usr.bin/mg/echo.c new file mode 100644 index 00000000000..3663cda638c --- /dev/null +++ b/usr.bin/mg/echo.c @@ -0,0 +1,781 @@ +/* + * 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. + */ +/* + * The varargs lint directive comments are 0 an attempt to get lint to shup + * up about CORRECT usage of varargs.h. It won't. + */ +#include "def.h" +#include "key.h" +#ifdef LOCAL_VARARGS +#include "varargs.h" +#else +#include <varargs.h> +#endif +#ifndef NO_MACRO +# include "macro.h" +#endif + +static int veread(); +VOID ewprintf(); +static VOID eformat(); +static VOID eputi(); +static VOID eputl(); +static VOID eputs(); +static VOID eputc(); +static int complt(); +static int complt_list(); +LIST * make_file_list(); +LIST * copy_list(); +char * strrchr(); +extern LIST * complete_function_list(); + +int epresf = FALSE; /* Stuff in echo line flag. */ + +extern int tthue; + +/* + * Erase the echo line. + */ +VOID +eerase() { + ttcolor(CTEXT); + ttmove(nrow-1, 0); + tteeol(); + ttflush(); + epresf = FALSE; +} + +/* + * Ask "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. + */ +eyorn(sp) char *sp; { + register int s; + +#ifndef NO_MACRO + if(inmacro) return TRUE; +#endif + ewprintf("%s? (y or n) ", sp); + for (;;) { + s = getkey(FALSE); + if (s == 'y' || s == 'Y') return TRUE; + if (s == 'n' || s == 'N') 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 question. User must type either all of + * "yes" or "no", and the trainling newline. + */ +eyesno(sp) char *sp; { + register int s; + char buf[64]; + +#ifndef NO_MACRO + if(inmacro) return TRUE; +#endif + s = ereply("%s? (yes or no) ", buf, sizeof(buf), sp); + for (;;) { + if (s == ABORT) return ABORT; + if (s != FALSE) { +#ifndef NO_MACRO + if (macrodef) { + LINE *lp = maclcur; + + maclcur = lp->l_bp; + maclcur->l_fp = lp->l_fp; + free((char *)lp); + } +#endif + if ((buf[0] == 'y' || buf[0] == 'Y') + && (buf[1] == 'e' || buf[1] == 'E') + && (buf[2] == 's' || buf[2] == 'S') + && (buf[3] == '\0')) return TRUE; + if ((buf[0] == 'n' || buf[0] == 'N') + && (buf[1] == 'o' || buf[0] == 'O') + && (buf[2] == '\0')) return FALSE; + } + s = ereply("Please answer yes or no. %s? (yes or no) ", + buf, sizeof(buf), sp); + } + /*NOTREACHED*/ +} +/* + * Write out a prompt, and read back a + * reply. The prompt is now written out with full "ewprintf" + * formatting, although the arguments are in a rather strange + * place. This is always a new message, there is no auto + * completion, and the return is echoed as such. + */ +/*VARARGS 0*/ +ereply(va_alist) +va_dcl +{ + va_list pvar; + register char *fp, *buf; + register int nbuf; + register int i; + + va_start(pvar); + fp = va_arg(pvar, char *); + buf = va_arg(pvar, char *); + nbuf = va_arg(pvar, int); + i = veread(fp, buf, nbuf, EFNEW|EFCR, &pvar); + va_end(pvar); + return i; +} + +/* + * 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"). The "flag" contains EFNEW (a + * new prompt), an EFFUNC (autocomplete), or EFCR (echo + * the carriage return as CR). + */ +/* VARARGS 0 */ +eread(va_alist) +va_dcl +{ + va_list pvar; + char *fp, *buf; + int nbuf, flag, i; + va_start(pvar); + fp = va_arg(pvar, char *); + buf = va_arg(pvar, char *); + nbuf = va_arg(pvar, int); + flag = va_arg(pvar, int); + i = veread(fp, buf, nbuf, flag, &pvar); + va_end(pvar); + return i; +} + +static veread(fp, buf, nbuf, flag, ap) char *fp; char *buf; va_list *ap; { + register int cpos; + register int i; + register int c; + +#ifndef NO_MACRO + if(inmacro) { + bcopy(maclcur->l_text, buf, maclcur->l_used); + buf[maclcur->l_used] = '\0'; + maclcur = maclcur->l_fp; + return TRUE; + } +#endif + cpos = 0; + if ((flag&EFNEW)!=0 || ttrow!=nrow-1) { + ttcolor(CTEXT); + ttmove(nrow-1, 0); + epresf = TRUE; + } else + eputc(' '); + eformat(fp, ap); + tteeol(); + ttflush(); + for (;;) { + c = getkey(FALSE); + if ((flag&EFAUTO) != 0 && (c == ' ' || c == CCHR('I'))) { + cpos += complt(flag, c, buf, cpos); + continue; + } + if ((flag&EFAUTO) != 0 && c == '?') { + complt_list(flag, c, buf, cpos); + continue; + } + switch (c) { + case CCHR('J'): + c = CCHR('M'); /* and continue */ + case CCHR('M'): /* Return, done. */ + if ((flag&EFFUNC) != 0) { + if ((i = complt(flag, c, buf, cpos)) == 0) + continue; + if (i > 0) cpos += i; + } + buf[cpos] = '\0'; + if ((flag&EFCR) != 0) { + ttputc(CCHR('M')); + ttflush(); + } +#ifndef NO_MACRO + if(macrodef) { + LINE *lp; + + if((lp = lalloc(cpos)) == NULL) return FALSE; + lp->l_fp = maclcur->l_fp; + maclcur->l_fp = lp; + lp->l_bp = maclcur; + maclcur = lp; + bcopy(buf, lp->l_text, cpos); + } +#endif + goto done; + + case CCHR('G'): /* Bell, abort. */ + eputc(CCHR('G')); + (VOID) ctrlg(FFRAND, 0); + ttflush(); + return ABORT; + + case CCHR('H'): + case CCHR('?'): /* Rubout, erase. */ + if (cpos != 0) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + if (ISCTRL(buf[--cpos]) != FALSE) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + } + ttflush(); + } + break; + + case CCHR('X'): /* C-X */ + case CCHR('U'): /* C-U, kill line. */ + while (cpos != 0) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + if (ISCTRL(buf[--cpos]) != FALSE) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + } + } + ttflush(); + break; + + case CCHR('W'): /* C-W, kill to beginning of */ + /* previous word */ + /* back up to first word character or beginning */ + while ((cpos > 0) && !ISWORD(buf[cpos - 1])) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + if (ISCTRL(buf[--cpos]) != FALSE) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + } + } + while ((cpos > 0) && ISWORD(buf[cpos - 1])) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + if (ISCTRL(buf[--cpos]) != FALSE) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + } + } + ttflush(); + break; + + case CCHR('\\'): + case CCHR('Q'): /* C-Q, quote next */ + c = getkey(FALSE); /* and continue */ + default: /* All the rest. */ + if (cpos < nbuf-1) { + buf[cpos++] = (char) c; + eputc((char) c); + ttflush(); + } + } + } +done: return buf[0] != '\0'; +} + +/* + * do completion on a list of objects. + */ +static int complt(flags, c, buf, cpos) +register char *buf; +register int cpos; +{ + register LIST *lh, *lh2; + LIST *wholelist = NULL; + int i, nxtra; + int nhits, bxtra; + int wflag = FALSE; + int msglen, nshown; + char *msg; + + if ((flags&EFFUNC) != 0) { + buf[cpos] = '\0'; + i = complete_function(buf, c); + if(i>0) { + eputs(&buf[cpos]); + ttflush(); + return i; + } + switch(i) { + case -3: + msg = " [Ambiguous]"; + break; + case -2: + i=0; + msg = " [No match]"; + break; + case -1: + case 0: + return i; + default: + msg = " [Internal error]"; + break; + } + } else { + if ((flags&EFBUF) != 0) lh = &(bheadp->b_list); + else if ((flags&EFFILE) != 0) { + buf[cpos] = '\0'; + wholelist = lh = make_file_list(buf,0); + } + 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; + + while (lh != NULL) { + for (i=0; i<cpos; ++i) { + if (buf[i] != lh->l_name[i]) + break; + } + if (i == cpos) { + if (nhits == 0) + lh2 = lh; + ++nhits; + if (lh->l_name[i] == '\0') nxtra = -1; + else { + bxtra = getxtra(lh, lh2, cpos, wflag); + if (bxtra < nxtra) nxtra = bxtra; + lh2 = lh; + } + } + lh = lh->l_next; + } + if (nhits == 0) + msg = " [No match]"; + else if (nhits > 1 && nxtra == 0) + msg = " [Ambiguous]"; + else { /* Got a match, do it to it */ + /* + * 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; ++i) { + buf[cpos] = lh2->l_name[cpos]; + eputc(buf[cpos++]); + } + ttflush(); + free_file_list(wholelist); + if (nxtra < 0 && c != CCHR('M')) return 0; + return nxtra; + } + } + /* 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! */ + return 0; +} + +/* + * do completion on a list of objects, listing instead of completing + */ +static int complt_list(flags, c, buf, cpos) +register char *buf; +register int cpos; +{ + register LIST *lh, *lh2, *lh3; + LIST *wholelist = NULL; + int i,maxwidth,width; + int preflen = 0; + BUFFER *bp; + static VOID findbind(); + int oldrow = ttrow; + int oldcol = ttcol; + int oldhue = tthue; + char linebuf[NCOL+1]; + char *cp; + + ttflush(); + + /* the results are put into a help buffer */ + + bp = bfind("*help*", TRUE); + if(bclear(bp) == FALSE) return FALSE; + + { /* this {} present for historical reasons */ + +/* + * 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, c); + } + else if ((flags&EFFILE) != 0) { + buf[cpos] = '\0'; + wholelist = lh = make_file_list(buf,1); + /* + * 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) { + lh3 = lh2->l_next; + while (lh3) { + 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. + */ + cp = linebuf; + width = 0; + lh2 = lh; + while (lh2 != NULL) { + for (i=0; i<cpos; ++i) { + if (buf[i] != lh2->l_name[i]) + break; + } + if (i == cpos) { + if ((width + maxwidth) > ncol) { + *cp = 0; + addline(bp,linebuf); + cp = linebuf; + width = 0; + } + strcpy(cp,lh2->l_name+preflen); + i = strlen(lh2->l_name+preflen); + cp += i; + for (; i < maxwidth; i++) + *cp++ = ' '; + width += maxwidth; + } + lh2 = lh2->l_next; + } + if (width > 0) { + *cp = 0; + addline(bp,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); /* 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. + */ +getxtra(lp1, lp2, cpos, wflag) register LIST *lp1, *lp2; register int wflag; { + register 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 0 */ +VOID +ewprintf(va_alist) +va_dcl +{ + va_list pvar; + register char *fp; + +#ifndef NO_MACRO + if(inmacro) return; +#endif + va_start(pvar); + fp = va_arg(pvar, char *); + ttcolor(CTEXT); + ttmove(nrow-1, 0); + eformat(fp, &pvar); + va_end(pvar); + 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. + * Note: %c works, and prints the "name" of the character. + * %k prints the name of a key (and takes no arguments). + */ +static VOID +eformat(fp, ap) +register char *fp; +register va_list *ap; +{ + register int c; + char kname[NKNAME]; + char *keyname(); + char *cp; + + while ((c = *fp++) != '\0') { + if (c != '%') + eputc(c); + else { + c = *fp++; + switch (c) { + case 'c': + (VOID) keyname(kname, va_arg(*ap, int)); + eputs(kname); + break; + + case 'k': + cp = kname; + for(c=0; c < key.k_count; c++) { + cp = keyname(cp, key.k_chars[c]); + *cp++ = ' '; + } + *--cp = '\0'; + eputs(kname); + break; + + case 'd': + eputi(va_arg(*ap, int), 10); + break; + + case 'o': + eputi(va_arg(*ap, int), 8); + break; + + case 's': + eputs(va_arg(*ap, char *)); + break; + + case 'l':/* explicit longword */ + c = *fp++; + switch(c) { + case 'd': + eputl((long)va_arg(*ap, long), 10); + break; + default: + eputc(c); + break; + } + break; + + default: + eputc(c); + } + } + } +} + +/* + * Put integer, in radix "r". + */ +static VOID +eputi(i, r) +register int i; +register int r; +{ + register 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(l, r) +register long l; +register int r; +{ + register 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(s) +register char *s; +{ + register int c; + + while ((c = *s++) != '\0') + eputc(c); +} + +/* + * Put character. Watch for + * control characters, and for the line + * getting too long. + */ +static VOID +eputc(c) +register char c; +{ + if (ttcol+2 < ncol) { + if (ISCTRL(c)) { + eputc('^'); + c = CCHR(c); + } + ttputc(c); + ++ttcol; + } +} + +free_file_list(lp) + LIST *lp; +{ +LIST *next; +while (lp) { + next = lp->l_next; + free(lp); + lp = next; +} +} + +LIST *copy_list(lp) + LIST *lp; +{ +LIST *current,*last; + +last = NULL; +while(lp) { + current = (LIST *)malloc(sizeof(LIST)); + current->l_next = last; + current->l_name = lp->l_name; + last = (LIST *)current; + lp = lp->l_next; +} +return(last); +} diff --git a/usr.bin/mg/extend.c b/usr.bin/mg/extend.c new file mode 100644 index 00000000000..b3d4aa4a298 --- /dev/null +++ b/usr.bin/mg/extend.c @@ -0,0 +1,797 @@ +/* + * Extended (M-X) commands, rebinding, and + * startup file processing. + */ +#include "def.h" +#include "kbd.h" + +#ifndef NO_MACRO +#include "macro.h" +#endif + +#ifdef FKEYS +#include "key.h" +#ifndef NO_STARTUP +#ifndef BINDKEY +#define BINDKEY /* bindkey is used by FKEYS startup code */ +#endif +#endif +#endif + +extern char *strncpy(); +extern int rescan(); + +/* insert a string, mainly for use from macros (created by selfinsert) */ +/*ARGSUSED*/ +insert(f, n) +int f, n; +{ + register char *cp; + char buf[128]; +#ifndef NO_MACRO + register int count; + int 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) thisflag |= CFINS; /* CFINS means selfinsert can tack on end */ +#endif + if(eread("Insert: ", buf, sizeof(buf), EFNEW) == FALSE) 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 extremly 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 verses 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. + */ +static int remap(curmap, c, funct, pref_map) +register KEYMAP *curmap;/* pointer to the map being changed */ +int c; /* character being changed */ +PF funct; /* function being changed to */ +KEYMAP *pref_map; /* if funct==prefix, map to bind to or NULL for new */ +/* extern MAP_ELEMENT *ele; must be set before calling */ +{ + register int i; + int n1, n2, nold; + KEYMAP *mp; + PF *pfp; + MAP_ELEMENT *mep; + static KEYMAP *realocmap(); + + if(ele >= &curmap->map_element[curmap->map_num] || c < ele->k_base) { + if(ele > &curmap->map_element[0] && (funct!=prefix || + (ele-1)->k_prefmap==NULL)) { + n1 = c - (ele-1)->k_num; + } else n1 = HUGE; + if(ele < &curmap->map_element[curmap->map_num] && (funct!=prefix || + ele->k_prefmap==NULL)) { + n2 = ele->k_base - c; + } else n2 = HUGE; + if(n1 <= MAPELEDEF && n1 <= n2) { + ele--; + if((pfp = (PF *)malloc((unsigned)(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 = (PF *)malloc((unsigned)(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 && + (curmap = realocmap(curmap)) == NULL) return FALSE; + if((pfp = (PF *)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 == prefix) { + if(pref_map != NULL) { + ele->k_prefmap = pref_map; + } else { + if((mp = (KEYMAP *)malloc(sizeof(KEYMAP) + + (MAPINIT-1)*sizeof(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!=prefix || pref_map==NULL || + pref_map==ele->k_prefmap)) + return TRUE; /* no change */ + if(funct!=prefix || ele->k_prefmap==NULL) { + if(ele->k_funcp[n1] == prefix) + ele->k_prefmap = (KEYMAP *)NULL; + ele->k_funcp[n1] = funct; /* easy case */ + if(funct==prefix) { + if(pref_map!=NULL) + ele->k_prefmap = pref_map; + else { + if((mp = (KEYMAP *)malloc(sizeof(KEYMAP) + + (MAPINIT-1)*sizeof(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] != prefix; + if(curmap->map_num >= curmap->map_max && + (curmap = realocmap(curmap)) == NULL) return FALSE; + if((pfp = (PF *)malloc((unsigned)(ele->k_num - c + !n2) + * sizeof(PF))) == NULL) { + ewprintf("Out of memory"); + return FALSE; + } + ele->k_funcp[n1] = prefix; + 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 = (KEYMAP *)malloc(sizeof(KEYMAP) + + (MAPINIT-1)*sizeof(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, used above */ +static KEYMAP *realocmap(curmap) +register KEYMAP *curmap; +{ + register KEYMAP *mp; + register int i; + static VOID fixmap(); + extern int nmaps; + + if((mp = (KEYMAP *)malloc((unsigned)(sizeof(KEYMAP)+ + (curmap->map_max+(MAPGROW-1))*sizeof(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(i=nmaps; i--; ) { + if(map_table[i].p_map == curmap) map_table[i].p_map = mp; + else fixmap(curmap, mp, map_table[i].p_map); + } + ele = &mp->map_element[ele - &curmap->map_element[0]]; + return mp; +} + +/* fix references to a reallocated keymap (recursive) */ +static VOID fixmap(curmap, mp, mt) +register KEYMAP *mt; +register KEYMAP *curmap; +KEYMAP *mp; +{ + register 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(curmap, p, unbind) +register KEYMAP *curmap; +char *p; +int unbind; +{ + PF funct; + char prompt[80]; + char *pep; + int c; + int s; + KEYMAP *pref_map = NULL; + +#ifndef NO_MACRO + if(macrodef) { + /* keystrokes arn'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; + } +#ifndef NO_STARTUP + if(inmacro) { + for(s=0; s < maclcur->l_used - 1; s++) { + if(doscan(curmap, c=CHARMASK(maclcur->l_text[s])) != prefix) { + if(remap(curmap, c, prefix, (KEYMAP *)NULL) != TRUE) { + return FALSE; + } + } + curmap = ele->k_prefmap; + } + (VOID) doscan(curmap, c=maclcur->l_text[s]); + maclcur = maclcur->l_fp; + } else { +#endif +#endif + (VOID) strcpy(prompt, p); + pep = prompt + strlen(prompt); + for(;;) { + ewprintf("%s", prompt); + pep[-1] = ' '; + pep = keyname(pep, c = getkey(FALSE)); + if(doscan(curmap,c) != prefix) break; + *pep++ = '-'; + *pep = '\0'; + curmap = ele->k_prefmap; + } +#ifndef NO_STARTUP + } +#endif + if(unbind) funct = rescan; + else { + if ((s=eread("%s to command: ", prompt, 80, EFFUNC|EFNEW, prompt)) + != TRUE) return s; + if (((funct = name_function(prompt)) == prefix) ? + (pref_map = name_map(prompt)) == 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 +bindkey(mapp, fname, keys, kcount) +KEYMAP **mapp; +char *fname; +KCHAR *keys; +int kcount; +{ + KEYMAP *curmap = *mapp; + PF funct; + int c; + KEYMAP *pref_map = NULL; + + if(fname == NULL) funct = rescan; + else if (((funct = name_function(fname)) == prefix) ? + (pref_map = name_map(fname)) == NULL : funct==NULL) { + ewprintf("[No match: %s]", fname); + return FALSE; + } + while(--kcount) { + if(doscan(curmap, c = *keys++) != prefix) { + if(remap(curmap, c, prefix, (KEYMAP *)NULL) != TRUE) + return FALSE; + } + curmap = ele->k_prefmap; + } + (VOID) doscan(curmap, c = *keys); + return remap(curmap, c, funct, pref_map); +} +#endif + +/* + * This function modifies the fundamental keyboard map. + */ +/*ARGSUSED*/ +bindtokey(f, n) +{ + return dobind(map_table[0].p_map, "Global set key: ", FALSE); +} + +/* + * This function modifies the current mode's keyboard map. + */ +/*ARGSUSED*/ +localbind(f, n) +{ + return dobind(curbp->b_modes[curbp->b_nmodes]->p_map, "Local set key: ", + FALSE); +} + +/* + * This function redefines a key in any keymap. + */ +/*ARGSUSED*/ +define_key(f, n) +{ + static char buf[48] = "Define key map: "; + MAPS *mp; + char *strncat(); + + buf[16] = '\0'; + if(eread(buf, &buf[16], 48 - 16, EFNEW) != TRUE) return FALSE; + if((mp = name_mode(&buf[16])) == NULL) { + ewprintf("Unknown map %s", &buf[16]); + return FALSE; + } + (VOID) strncat(&buf[16], " key: ", 48-16-1); + return dobind(mp->p_map, buf, FALSE); +} + +unbindtokey(f, n) +int f, n; +{ + return dobind(map_table[0].p_map, "Global unset key: ", TRUE); +} + +localunbind(f, n) +int f, 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. + */ +extend(f, n) +{ + PF funct; + int s; + char xname[NXNAME]; + + if(!(f & FFARG)) s = eread("M-x ", xname, NXNAME, EFNEW|EFFUNC); + else s = eread("%d M-x ", xname, NXNAME, EFNEW|EFFUNC, n); + if(s != TRUE) return s; + if((funct = name_function(xname)) != NULL) { +#ifndef NO_MACRO + if(macrodef) { + LINE *lp = maclcur; + macro[macrocount-1].m_funct = funct; + maclcur = lp->l_bp; + maclcur->l_fp = lp->l_fp; + free((char *)lp); + } +#endif + return (*funct)(f, n); + } + ewprintf("[No match]"); + return FALSE; +} + +#ifndef NO_STARTUP +/* + * 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*/ +evalexpr(f, n) +{ + int s; + char exbuf[128]; + + if ((s = ereply("Eval: ", exbuf, 128)) != TRUE) + return s; + return excline(exbuf); +} +/* + * evalbuffer - evaluate the current buffer as line commands. Useful + * for testing startup files. + */ +/*ARGSUSED*/ +evalbuffer(f, n) +{ + register LINE *lp; + register BUFFER *bp = curbp; + register int s; + static char excbuf[128]; + + for (lp = lforw(bp->b_linep); lp != bp->b_linep; lp = lforw(lp)) { + if (llength(lp) >= 128) return FALSE; + (VOID) strncpy(excbuf, ltext(lp), llength(lp)); + excbuf[llength(lp)] = '\0'; /* make sure it's terminated */ + 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*/ +evalfile(f, n) +{ + register int s; + char fname[NFILEN]; + + if ((s = ereply("Load file: ", fname, NFILEN)) != TRUE) + return s; + return load(fname); +} + +/* + * load - go load the file name we got passed. + */ +load(fname) char *fname; { + int s = TRUE; + int nbytes; + char excbuf[128]; + + if ((fname = adjustname(fname)) == NULL) + return FALSE; /* just to be careful */ + + if (ffropen(fname, (BUFFER *) NULL) != FIOSUC) return FALSE; + while ((s = ffgetline(excbuf, sizeof(excbuf)-1, &nbytes)) == FIOSUC) { + excbuf[nbytes] = '\0'; + if (excline(excbuf) != TRUE) { + s = FIOERR; + ewprintf("Error loading file %s", fname); + break; + } + } + (VOID) ffclose((BUFFER *) 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 functionallity of dobind so function + * key values don't have to fit in type char. + */ +excline(line) +register char *line; +{ + register char *funcp, *argp = NULL; + register int c; + int status; + int f, n; + LINE *lp, *np; + PF fp; +#ifdef FKEYS + int bind; + KEYMAP *curmap; + MAPS *mp; +#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 +#define BINDEXT 0 +#endif + PF name_function(); + LINE *lalloc(); + static char *skipwhite(), *parsetoken(); + + 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 ((*line >= '0' && *line <= '9') || *line == '-') { + argp = line; + line = parsetoken(line); + } + } + + if (argp != NULL) { + f = FFARG; + n = atoi(argp); + } + if((fp = name_function(funcp)) == NULL) { + ewprintf("Unknown function: %s", funcp); + return FALSE; + } +#ifdef FKEYS + if(fp == bindtokey || fp == unbindtokey) { + bind = BINDARG; + curmap = map_table[0].p_map; + } else if(fp == localbind || fp == localunbind) { + bind = BINDARG; + curmap = curbp->b_modes[curbp->b_nmodes]->p_map; + } else if(fp == define_key) bind = BINDNEXT; + else bind = BINDNO; +#endif + /* 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 + lp->l_used--; /* don't count BINDEXT! */ + if(bind == BINDARG) bind = BINDNO; +#endif + } else { /* Quoted strings special */ + ++argp; +#ifdef FKEYS + if(bind != BINDARG) { +#endif + if((lp = lalloc((int)(line-argp)+BINDEXT))==NULL) { + status = FALSE; + goto cleanup; + } + lp->l_used = 0; +#ifdef FKEYS + } else { + key.k_count = 0; + } +#endif + 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 + default: + c = CHARMASK(*argp); + break; + } + argp++; + } +#ifdef FKEYS + if(bind == BINDARG) + key.k_chars[key.k_count++] = c; + else +#endif + 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((mp = name_mode(lp->l_text)) == NULL) { + ewprintf("No such mode: %s", lp->l_text); + status = FALSE; + free((char *)lp); + goto cleanup; + } + curmap = mp->p_map; + free((char *)lp); + bind = BINDARG; + break; + default: +#endif + lp->l_fp = np->l_fp; + lp->l_bp = np; + np->l_fp = lp; + np = lp; +#ifdef FKEYS + } +#endif + } +#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, (char *)NULL, key.k_chars, key.k_count); + break; + case BINDNO: +#endif + inmacro = TRUE; + maclcur = maclcur->l_fp; + status = (*fp)(f, n); + inmacro = FALSE; +#ifdef FKEYS + } +#endif +cleanup: + lp = maclcur->l_fp; + while(lp!=maclcur) { + np = lp->l_fp; + free((char *)lp); + lp = np; + } + free((char *)lp); + return status; +} + +/* + * a pair of utility functions for the above + */ +static char * +skipwhite(s) +register char *s; +{ + while(*s == ' ' || *s == '\t' || *s == ')' || *s == '(') s++; + if (*s == ';') *s = '\0' ; + return s; +} + +static char * +parsetoken(s) +register 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; +} +#endif diff --git a/usr.bin/mg/file.c b/usr.bin/mg/file.c new file mode 100644 index 00000000000..f75aa6a7e3a --- /dev/null +++ b/usr.bin/mg/file.c @@ -0,0 +1,482 @@ +/* + * File commands. + */ +#include "def.h" + +BUFFER *findbuffer(); +VOID makename(); +VOID upmodes(); +static char *itos(); + +/* + * insert a file into the current buffer. Real easy - just call the + * insertfile routine with the file name. + */ +/*ARGSUSED*/ +fileinsert(f, n) +{ + register int s; + char fname[NFILEN]; + + if ((s=eread("Insert file: ", fname, NFILEN, EFNEW|EFCR|EFFILE)) != TRUE) + return (s); + return insertfile(adjustname(fname), (char *) NULL, FALSE); + /* don't set buffer name */ +} + +/* + * Select a file for editing. + * Look around to see if you can find the + * fine 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*/ +filevisit(f, n) +{ + register BUFFER *bp; + int s; + char fname[NFILEN]; + char *adjf; + + if ((s=eread("Find file: ", fname, NFILEN, EFNEW|EFCR|EFFILE)) != TRUE) + return s; + adjf = adjustname(fname); + if ((bp = findbuffer(adjf)) == NULL) return FALSE; + curbp = bp; + if (showbuffer(bp, curwp, WFHARD) != TRUE) return FALSE; + if (bp->b_fname[0] == 0) + return readin(adjf); /* Read it in. */ + return TRUE; +} + +/* + * Pop to a file in the other window. Same as last function, just + * popbuf instead of showbuffer. + */ +/*ARGSUSED*/ +poptofile(f, n) +{ + register BUFFER *bp; + register WINDOW *wp; + int s; + char fname[NFILEN]; + char *adjf; + + if ((s=eread("Find file in other window: ", fname, NFILEN, + EFNEW|EFCR|EFFILE)) != TRUE) + return s; + adjf = adjustname(fname); + if ((bp = findbuffer(adjf)) == NULL) return FALSE; + if ((wp = popbuf(bp)) == NULL) return FALSE; + curbp = bp; + curwp = wp; + if (bp->b_fname[0] == 0) + return readin(adjf); /* Read it in. */ + return TRUE; +} + +/* + * given a file name, either find the buffer it uses, or create a new + * empty buffer to put it in. + */ +BUFFER * +findbuffer(fname) +char *fname; +{ + register BUFFER *bp; + char bname[NBUFN], *cp; + unsigned count = 1; + + for (bp=bheadp; bp!=NULL; bp=bp->b_bufp) { + if (fncmp(bp->b_fname, fname) == 0) + return bp; + } + makename(bname, fname); /* New buffer name. */ + cp = bname + strlen(bname); + while(bfind(bname, FALSE) != NULL) { + *cp = '<'; /* add "<count>" to then name */ + (VOID) strcpy(itos(cp, ++count)+1, ">"); + } + return bfind(bname, TRUE); +} + +/* + * Put the decimal representation of num into a buffer. Hacked to be + * faster, smaller, and less general. + */ +static char *itos(bufp, num) +char *bufp; +unsigned num; +{ + if (num >= 10) { + bufp = itos(bufp, num/10); + num %= 10; + } + *++bufp = '0' + num; + return bufp; +} + +/* + * 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 "uemacs file"). + */ +readin(fname) char *fname; { + register int status; + register WINDOW *wp; + + if (bclear(curbp) != TRUE) /* Might be old. */ + return TRUE; + status = insertfile(fname, fname, TRUE) ; + curbp->b_flag &= ~BFCHG; /* No change. */ + for (wp=wheadp; wp!=NULL; wp=wp->w_wndp) { + if (wp->w_bufp == curbp) { + wp->w_dotp = wp->w_linep = lforw(curbp->b_linep); + wp->w_doto = 0; + wp->w_markp = NULL; + wp->w_marko = 0; + } + } + 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. 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. If the + * BACKUP conditional is set, then 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; + +insertfile(fname, newname, needinfo) char fname[], newname[]; { + register LINE *lp1; + register LINE *lp2; + register WINDOW *wp; + int nbytes; + LINE *olp; /* Line we started at */ + int opos; /* and offset into it */ + int s, nline; + BUFFER *bp; + + if (line == NULL) { + line = malloc(NLINE); + linesize = NLINE; + } + bp = curbp; /* Cheap. */ + if (newname != (char *) NULL) + (VOID) strcpy(bp->b_fname, newname); + /* Hard file open. */ + if ((s=ffropen(fname, needinfo ? bp : (BUFFER *) NULL)) == FIOERR) + goto out; + if (s == FIOFNF) { /* File not found. */ + if (newname != NULL) + ewprintf("(New file)"); + else ewprintf("(File not found)"); + goto out; + } + opos = curwp->w_doto; + /* Open a new line, at point, and start inserting after it */ + (VOID) lnewline(); + olp = lback(curwp->w_dotp); + if(olp == curbp->b_linep) { + /* if at end of buffer, create a line to insert before */ + (VOID) lnewline(); + curwp->w_dotp = lback(curwp->w_dotp); + } + nline = 0; /* Don't count fake line at end */ + while ((s=ffgetline(line, linesize, &nbytes)) != FIOERR) { +doneread: + switch(s) { + case FIOSUC: + ++nline; + /* and continue */ + case FIOEOF: /* the last line of the file */ + if ((lp1=lalloc(nbytes)) == NULL) { + s = FIOERR; /* Keep message on the */ + goto endoffile; /* display. */ + } + bcopy(line, <ext(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) goto endoffile; + break; + case FIOLONG: { /* a line to long to fit in our buffer */ + char *cp; + int newsize; + + newsize = linesize * 2; + if(newsize < 0 || + (cp = malloc((unsigned)newsize)) == NULL) { + ewprintf("Could not allocate %d bytes", + newsize); + s = FIOERR; + goto endoffile; + } + bcopy(line, cp, linesize); + free(line); + line = cp; + s=ffgetline(line+linesize, linesize, &nbytes); + nbytes += linesize; + linesize = newsize; + if (s == FIOERR) + goto endoffile; + goto doneread; + } + default: + ewprintf("Unknown code %d reading file", s); + s = FIOERR; + break; + } + } +endoffile: + (VOID) ffclose((BUFFER *) NULL); /* Ignore errors. */ + if (s==FIOEOF) { /* Don't zap an error. */ + 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); + (VOID) ldelnewline(); + curwp->w_dotp = olp; + curwp->w_doto = opos; + if(olp == curbp->b_linep) curwp->w_dotp = lforw(olp); +#ifndef NO_BACKUP + if (newname != NULL) + bp->b_flag |= BFCHG | BFBAK; /* Need a backup. */ + else bp->b_flag |= BFCHG; +#else + bp->b_flag |= BFCHG; +#endif + /* 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_linep; + if (curwp->w_markp == lp1) { + lp2 = curwp->w_dotp; + } else { + (VOID) ldelnewline(); /* delete extranious newline */ +out: lp2 = NULL; + } + for (wp=wheadp; wp!=NULL; wp=wp->w_wndp) { + if (wp->w_bufp == curbp) { + wp->w_flag |= 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; + } + } + } + return s != FIOERR; /* False if error. */ +} + +/* + * Take a file name, and from it + * fabricate a buffer name. This routine knows + * about the syntax of file names on the target system. + * BDC1 left scan delimiter. + * BDC2 optional second left scan delimiter. + * BDC3 optional right scan delimiter. + */ +VOID +makename(bname, fname) char bname[]; char fname[]; { + register char *cp1; + register char *cp2; + + cp1 = &fname[0]; + while (*cp1 != 0) + ++cp1; + --cp1; /* insure at least 1 character ! */ +#ifdef BDC2 + while (cp1!=&fname[0] && cp1[-1]!=BDC1 && cp1[-1]!=BDC2) + --cp1; +#else + while (cp1!=&fname[0] && cp1[-1]!=BDC1) + --cp1; +#endif + cp2 = &bname[0]; +#ifdef BDC3 + while (cp2!=&bname[NBUFN-1] && *cp1!=0 && *cp1!=BDC3) + *cp2++ = *cp1++; +#else + while (cp2!=&bname[NBUFN-1] && *cp1!=0) + *cp2++ = *cp1++; +#endif + *cp2 = 0; +} + +/* + * 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 compatable with Gosling EMACS than + * with ITS EMACS. + */ +/*ARGSUSED*/ +filewrite(f, n) +{ + register int s; + char fname[NFILEN]; + char *adjfname; + + if ((s=eread("Write file: ", fname, NFILEN, + EFNEW|EFCR|EFFILE)) != TRUE) + return (s); + adjfname = adjustname(fname); + /* old attributes are no longer current */ + bzero(&curbp->b_fi, sizeof(curbp->b_fi)); + if ((s=writeout(curbp, adjfname)) == TRUE) { + (VOID) strcpy(curbp->b_fname, adjfname); +#ifndef NO_BACKUP + curbp->b_flag &= ~(BFBAK | BFCHG); +#else + curbp->b_flag &= ~BFCHG; +#endif + upmodes(curbp); + } + return s; +} + +/* + * Save the contents of the current buffer back into + * its associated file. + */ +#ifndef NO_BACKUP +#ifndef MAKEBACKUP +#define MAKEBACKUP TRUE +#endif +static int makebackup = MAKEBACKUP; +#endif + +/*ARGSUSED*/ +filesave(f, n) +{ + 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. + */ +buffsave(bp) BUFFER *bp; { + register int s; + + if ((bp->b_flag&BFCHG) == 0) { /* Return, no changes. */ + ewprintf("(No changes need to be saved)"); + return TRUE; + } + if (bp->b_fname[0] == '\0') { /* Must have a name. */ + ewprintf("No file name"); + return (FALSE); + } +#ifndef NO_BACKUP + if (makebackup && (bp->b_flag&BFBAK)) { + s = fbackupfile(bp->b_fname); + if (s == ABORT) /* Hard error. */ + return FALSE; + if (s == FALSE /* Softer error. */ + && (s=eyesno("Backup error, save anyway")) != TRUE) + return s; + } +#endif + if ((s=writeout(bp, bp->b_fname)) == TRUE) { +#ifndef NO_BACKUP + bp->b_flag &= ~(BFCHG | BFBAK); +#else + bp->b_flag &= ~BFCHG; +#endif + upmodes(bp); + } + return s; +} + +#ifndef NO_BACKUP +/* 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. Only used when BACKUP + * is #defined. + */ +/*ARGSUSED*/ +makebkfile(f, n) +{ + if(f & FFARG) makebackup = n > 0; + else makebackup = !makebackup; + ewprintf("Backup files %sabled", makebackup ? "en" : "dis"); + return TRUE; +} +#endif + +/* + * 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. + */ +writeout(bp, fn) register BUFFER *bp; char *fn; { + register int s; + + if ((s=ffwopen(fn,bp)) != FIOSUC) /* Open writes message. */ + return (FALSE); + s = ffputbuf(bp); + if (s == FIOSUC) { /* No write error. */ + s = ffclose(bp); + if (s==FIOSUC) + ewprintf("Wrote %s", fn); + } else /* Ignore close error */ + (VOID) ffclose(bp); /* if a write error. */ + return s == FIOSUC; +} + +/* + * Tag all windows for bp (all windows if bp NULL) as needing their + * mode line updated. + */ +VOID +upmodes(bp) register BUFFER *bp; { + register WINDOW *wp; + + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) + if (bp == NULL || curwp->w_bufp == bp) wp->w_flag |= WFMODE; +} diff --git a/usr.bin/mg/fileio.c b/usr.bin/mg/fileio.c new file mode 100644 index 00000000000..7f5ab6c34c4 --- /dev/null +++ b/usr.bin/mg/fileio.c @@ -0,0 +1,628 @@ +/* + * sys V fileio.c + */ +#include "def.h" + +static FILE *ffp; +extern char *getenv(); +char *adjustname(); + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/dir.h> + +/* + * Open a file for reading. + */ +ffropen(fn, bp) char *fn; BUFFER *bp; { + struct stat statbuf; + + if ((ffp=fopen(fn, "r")) == NULL) + return (FIOFNF); + if (bp && fstat(fileno(ffp), &statbuf) == 0) { +/* set highorder bit to make sure this isn't all zero */ + bp->b_fi.fi_mode = statbuf.st_mode | 0x8000; + bp->b_fi.fi_uid = statbuf.st_uid; + bp->b_fi.fi_gid = statbuf.st_gid; + } + return (FIOSUC); +} + +/* + * Open a file for writing. + * Return TRUE if all is well, and + * FALSE on error (cannot create). + */ +ffwopen(fn, bp) char *fn; BUFFER *bp; { + if ((ffp=fopen(fn, "w")) == NULL) { + ewprintf("Cannot open file for writing"); + 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' 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) { + chmod(fn, bp->b_fi.fi_mode & 07777); + chown(fn, bp->b_fi.fi_uid, bp->b_fi.fi_gid); + } + return (FIOSUC); +} + +/* + * Close a file. + * Should look at the status. + */ +ffclose() { + (VOID) fclose(ffp); + return (FIOSUC); +} + +/* + * Write a buffer to the already + * opened file. bp points to the + * buffer. Return the status. + * Check only at the newline and + * end of buffer. + */ +ffputbuf(bp) +BUFFER *bp; +{ + register char *cp; + register char *cpend; + register LINE *lp; + register LINE *lpend; + + lpend = bp->b_linep; + lp = lforw(lpend); + do { + cp = <ext(lp)[0]; /* begining of line */ + cpend = &cp[llength(lp)]; /* end of line */ + while(cp != cpend) { + putc(*cp, ffp); + cp++; /* putc may evalualte arguments more than once */ + } + lp = lforw(lp); + if(lp == lpend) break; /* no implied newline on last line */ + putc('\n', ffp); + } while(!ferror(ffp)); + if(ferror(ffp)) { + ewprintf("Write I/O error"); + return FIOERR; + } + 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. + */ +ffgetline(buf, nbuf, nbytes) +register char *buf; +register int nbuf; +register int *nbytes; +{ + register int c; + register int 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; +} + +#ifndef NO_BACKUP +/* + * Rename the file "fname" into a backup + * copy. 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". The "unlink" is perhaps not the + * right thing here; I don't care that much as + * I don't enable backups myself. + */ +fbackupfile(fn) char *fn; { + register char *nname; + char *malloc(), *strrchr(); + int i; + char *lastpart; + + if ((nname=malloc((unsigned)(strlen(fn)+1+1))) == NULL) { + ewprintf("Can't get %d bytes", strlen(fn) + 1); + return (ABORT); + } + (void) strcpy(nname, fn); +/* + * with BSD, just strcat the ~. But SV has a max file name of 14, so + * we have to check. + */ + lastpart = strrchr(nname, '/'); + if (lastpart) + lastpart++; + else + lastpart = nname; + i = strlen(lastpart); + if (i > 13) + if (lastpart[13] == '~') { /* already a backup name */ + free(nname); + return(FALSE); + } + else + lastpart[13] = '~'; + else { + lastpart[i] = '~'; + lastpart[i+1] = 0; + } + (void) unlink(nname); /* Ignore errors. */ + if (link(fn, nname) != 0 || unlink(fn) != 0) { + free(nname); + return (FALSE); + } + free(nname); + return (TRUE); +} +#endif + +/* + * The string "fn" is a file name. + * Perform any required appending of directory name or case adjustments. + * If NO_DIR is not defined, the same file should be refered to even if the + * working directory changes. + */ +#ifdef SYMBLINK +#include <sys/types.h> +#include <sys/stat.h> +#ifndef MAXLINK +#define MAXLINK 8 /* maximum symbolic links to follow */ +#endif +#endif +#include <pwd.h> +#ifndef NO_DIR +extern char *wdir; +#endif + +char *adjustname(fn) +register char *fn; +{ + register char *cp; + static char fnb[NFILEN]; + extern struct passwd *getpwnam(); + struct passwd *pwent; +#ifdef SYMBLINK + struct stat statbuf; + int i, j; + char linkbuf[NFILEN]; +#endif + + switch(*fn) { + case '/': + cp = fnb; + *cp++ = *fn++; + break; + case '~': + fn++; + if(*fn == '/' || *fn == '\0') { + (VOID) strcpy(fnb, getenv("HOME")); + cp = fnb + strlen(fnb); + if(*fn) fn++; + break; + } else { + cp = fnb; + while(*fn && *fn != '/') *cp++ = *fn++; + *cp = '\0'; + if((pwent = getpwnam(fnb)) != NULL) { + (VOID) strcpy(fnb, pwent->pw_dir); + cp = fnb + strlen(fnb); + break; + } else { + fn -= strlen(fnb) + 1; + /* can't find ~user, continue to default case */ + } + } + default: +#ifndef NODIR + strcpy(fnb, wdir); + cp = fnb + strlen(fnb); + break; +#else + return fn; /* punt */ +#endif + } + if(cp != fnb && cp[-1] != '/') *cp++ = '/'; + while(*fn) { + switch(*fn) { + case '.': + switch(fn[1]) { + case '\0': + *--cp = '\0'; + return fnb; + case '/': + fn += 2; + continue; + case '.': + if(fn[2]=='/' || fn[2] == '\0') { +#ifdef SYMBLINK + cp[-1] = '\0'; + for(j = MAXLINK; j-- && + lstat(fnb, &statbuf) != -1 && + (statbuf.st_mode&S_IFMT) == S_IFLNK && + (i = readlink(fnb, linkbuf, sizeof linkbuf)) + != -1 ;) { + if(linkbuf[0] != '/') { + --cp; + while(cp > fnb && *--cp != '/') {} + ++cp; + (VOID) strncpy(cp, linkbuf, i); + cp += i; + } else { + (VOID) strncpy(fnb, linkbuf, i); + cp = fnb + i; + } + if(cp[-1]!='/') *cp++ = '\0'; + else cp[-1] = '\0'; + } + cp[-1] = '/'; +#endif + --cp; + while(cp > fnb && *--cp != '/') {} + ++cp; + if(fn[2]=='\0') { + *--cp = '\0'; + return fnb; + } + fn += 3; + continue; + } + break; + default: + break; + } + break; + case '/': + fn++; + continue; + default: + break; + } + while(*fn && (*cp++ = *fn++) != '/') {} + } + if(cp[-1]=='/') --cp; + *cp = '\0'; + return fnb; +} + +#ifndef NO_STARTUP +#include <sys/file.h> +#define F_OK 04 /* for stupid Sys V */ + +/* + * 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(suffix) +char *suffix; +{ + register char *file; + static char home[NFILEN]; + char *getenv(); + + if ((file = getenv("HOME")) == NULL) goto notfound; + if (strlen(file)+7 >= NFILEN - 1) goto notfound; + (VOID) strcpy(home, file); + (VOID) strcat(home, "/.mg"); + if (suffix != NULL) { + (VOID) strcat(home, "-"); + (VOID) strcat(home, suffix); + } + if (access(home, F_OK) == 0) return home; + +notfound: +#ifdef STARTUPFILE + file = STARTUPFILE; + if (suffix != NULL) { + (VOID) strcpy(home, file); + (VOID) strcat(home, "-"); + (VOID) strcat(home, suffix); + file = home; + } + if (access(file, F_OK ) == 0) return file; +#endif + + return NULL; +} +#endif + +#ifndef NO_DIRED +#include "kbd.h" + +/* + * It's sort of gross to call system commands in a subfork. However + * that's by far the easiest way. These things are used only in + * dired, so they are not performance-critical. The cp program is + * almost certainly faster at copying files than any stupid code that + * we would write. In fact there is no other way to do unlinkdir. + * You don't want to do a simple unlink. To do the whole thing in + * a general way requires root, and rmdir is setuid. We don't really + * want microemacs to have to run setuid. rename is easy to do with + * unlink, link, unlink. However that code will fail if the destination + * is on a different file system. mv will copy in that case. It seems + * easier to let mv worry about this stuff. + */ + +copy(frname, toname) +char *frname, *toname; +{ + int pid; + int status; + + if(pid = fork()) { + if(pid == -1) return -1; + execl("/bin/cp", "cp", frname, toname, (char *)NULL); + _exit(1); /* shouldn't happen */ + } + while(wait(&status) != pid) + ; + return status == 0; +} + +BUFFER *dired_(dirname) +char *dirname; +{ + register BUFFER *bp; + char line[256]; + BUFFER *findbuffer(); + FILE *dirpipe; + FILE *popen(); + char *strncpy(); + + if((dirname = adjustname(dirname)) == NULL) { + ewprintf("Bad directory name"); + return NULL; + } + if((bp = findbuffer(dirname)) == NULL) { + ewprintf("Could not create buffer"); + return NULL; + } + if(bclear(bp) != TRUE) return FALSE; + (VOID) strcpy(line, "ls -al "); + (VOID) strcpy(&line[7], dirname); + if((dirpipe = popen(line, "r")) == NULL) { + ewprintf("Problem opening pipe to ls"); + return NULL; + } + line[0] = line[1] = ' '; + while(fgets(&line[2], 254, dirpipe) != NULL) { + line[strlen(line) - 1] = '\0'; /* remove ^J */ + (VOID) addline(bp, line); + } + if(pclose(dirpipe) == -1) { + ewprintf("Problem closing pipe to ls"); + return NULL; + } + bp->b_dotp = lforw(bp->b_linep); /* go to first line */ + (VOID) strncpy(bp->b_fname, dirname, NFILEN); + if((bp->b_modes[0] = name_mode("dired")) == NULL) { + bp->b_modes[0] = &map_table[0]; + ewprintf("Could not find mode dired"); + return NULL; + } + bp->b_nmodes = 0; + return bp; +} + +d_makename(lp, fn) +register LINE *lp; +register char *fn; +{ + register char *cp; + + if(llength(lp) <= 56) return ABORT; + (VOID) strcpy(fn, curbp->b_fname); + cp = fn + strlen(fn); + bcopy(&lp->l_text[56], cp, llength(lp) - 56); + cp[llength(lp) - 56] = '\0'; + return lgetc(lp, 2) == 'd'; +} + +/* + * I, a System V novice, could only figure out how to do unlinkdir() + * and rename() as exec's of the appropriate functions. So sue me. + * --Stephen Walton, December 1987 + */ + +unlinkdir(f) +char *f; +{ + int status, pid, wpid; + + if ((pid = fork()) == 0) + execl("/bin/rmdir", "rmdir", f, (char *)NULL); + else if (pid > 0) + while ((wpid = wait(&status)) && wpid != pid) + ; + else + return FALSE; + return status == 0; +} + +Xrename(f1, f2) +char *f1, *f2; +{ + + int status, pid, wpid; + + if ((pid = fork()) == 0) + execl("/bin/mv", "mv", f1, f2, (char *)NULL); + else if (pid > 0) + while ((wpid = wait(&status)) && wpid != pid) + ; + else + return FALSE; + return status == 0; +} +#endif NO_DIRED + +struct filelist { + LIST fl_l; + char fl_name[NFILEN+2]; +}; + +/* these things had better be contiguous, because we're going + * to refer to the end of dirbuf + 1 byte */ +struct direct dirbuf; +char dirdummy; + +/* + * return list of file names that match the name in buf. + * System V version. listing is a flag indicating whether the + * list is being used for printing a listing rather than + * completion. In that case, trailing * and / are put on + * for executables and directories. The list is not sorted. + */ + +LIST *make_file_list(buf,listing) + char *buf; + int listing; +{ +char *dir,*file,*cp; +int len,i,preflen; +int fp; +LIST *last,*l1,*l2; +struct filelist *current; +char prefixx[NFILEN+1]; +extern int errno; +struct stat statbuf; +char statname[NFILEN+2]; + +/* 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/.. as a reference to another directory, + * whereas we are interested in names starting with .. + */ +len = strlen(buf); +if (buf[len-1] == '.') { + buf[len-1] = 'x'; + dir = adjustname(buf); + buf[len-1] = '.'; +} +else + dir = adjustname(buf); +/* + * if the user typed a trailing / or the empty string + * he wants us to use his file spec as a directory name + */ +if (buf[0] && buf[strlen(buf)-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 */ +strcpy(prefixx,buf); +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 + DIRSIZ) > NFILEN) + return(NULL); +if ((strlen(dir) + DIRSIZ) > NFILEN) + listing = 0; + +/* 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. + */ + +fp = open(dir,0); +if (fp < 0) { + return(NULL); +} + +last = NULL; +/* clear entry after last so we can treat d_name as ASCIZ */ +dirbuf.d_name[DIRSIZ] = 0; +while (1) { + if (read(fp, &dirbuf, sizeof(struct direct)) <= 0) { + break; + } + if (dirbuf.d_ino == 0) /* entry not allocated */ + continue; + for (i=0; i<len; ++i) { + if (cp[i] != dirbuf.d_name[i]) + break; + } + if (i < len) + continue; + current = (struct filelist *)malloc(sizeof(struct filelist)); + current->fl_l.l_next = last; + current->fl_l.l_name = current->fl_name; + last = (LIST *)current; + strcpy(current->fl_name,prefixx); + strcat(current->fl_name,dirbuf.d_name); + if (listing) { + statbuf.st_mode = 0; + strcpy(statname,dir); + strcat(statname,"/"); + strcat(statname,dirbuf.d_name); + stat(statname,&statbuf); + if (statbuf.st_mode & 040000) + strcat(current->fl_name,"/"); + else if (statbuf.st_mode & 0100) + strcat(current->fl_name,"*"); + } + +} +close(fp); + +return (last); + +} diff --git a/usr.bin/mg/help.c b/usr.bin/mg/help.c new file mode 100644 index 00000000000..ae9268aa85a --- /dev/null +++ b/usr.bin/mg/help.c @@ -0,0 +1,270 @@ +/* Help functions for MicroGnuEmacs 2 */ + +#include "def.h" + +#ifndef NO_HELP +#include "kbd.h" +#include "key.h" +#ifndef NO_MACRO +#include "macro.h" +#endif +extern int rescan(); + +/* + * 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*/ +desckey(f, n) +{ + register KEYMAP *curmap; + register PF funct; + register char *pep; + char prompt[80]; + int c; + int m; + int i; + +#ifndef NO_MACRO + if(inmacro) return TRUE; /* ignore inside keyboard macro */ +#endif + (VOID) strcpy(prompt, "Describe key briefly: "); + pep = prompt + strlen(prompt); + key.k_count = 0; + m = curbp->b_nmodes; + curmap = curbp->b_modes[m]->p_map; + for(;;) { + for(;;) { + ewprintf("%s", prompt); + pep[-1] = ' '; + pep = keyname(pep, key.k_chars[key.k_count++] = c = getkey(FALSE)); + if((funct = doscan(curmap, c)) != prefix) break; + *pep++ = '-'; + *pep = '\0'; + curmap = ele->k_prefmap; + } + if(funct != rescan) break; + if(ISUPPER(key.k_chars[key.k_count-1])) { + funct = doscan(curmap, TOLOWER(key.k_chars[key.k_count-1])); + if(funct == prefix) { + *pep++ = '-'; + *pep = '\0'; + curmap = ele->k_prefmap; + 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]); + if(funct != prefix) { + if(i == key.k_count - 1 && funct != rescan) goto found; + funct = rescan; + goto nextmode; + } + curmap = ele->k_prefmap; + } + *pep++ = '-'; + *pep = '\0'; + } +found: + if(funct == rescan) ewprintf("%k is not bound to any function"); + else if((pep = 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 MicroGnuEMACS produce it's own wall chart. + */ +static BUFFER *bp; +static char buf[80]; /* used by showall and findbind */ + +/*ARGSUSED*/ +wallchart(f, n) +{ + int m; + static char locbind[80] = "Local keybindings for mode "; + static int showall(); + + bp = bfind("*help*", TRUE); + if (bclear(bp) != TRUE) return FALSE; /* Clear it out. */ + for(m=curbp->b_nmodes; m > 0; m--) { + (VOID) strcpy(&locbind[27], curbp->b_modes[m]->p_name); + (VOID) strcat(&locbind[27], ":"); + if((addline(bp, locbind) == FALSE) || + (showall(buf, curbp->b_modes[m]->p_map) == FALSE) || + (addline(bp, "") == FALSE)) return FALSE; + } + if((addline(bp, "Global bindings:") == FALSE) || + (showall(buf, map_table[0].p_map) == FALSE)) return FALSE; + return popbuftop(bp); +} + +static int showall(ind, map) +char *ind; +KEYMAP *map; +{ + register MAP_ELEMENT *ele; + register int i; + PF functp; + char *cp; + char *cp2; + int last; + + if(addline(bp, "") == FALSE) return FALSE; + last = -1; + for(ele = &map->map_element[0]; ele < &map->map_element[map->map_num] ; ele++) { + if(map->map_default != rescan && ++last < ele->k_base) { + cp = keyname(ind, last); + if(last < ele->k_base - 1) { + (VOID) strcpy(cp, " .. "); + cp = keyname(cp + 4, ele->k_base - 1); + } + do { *cp++ = ' '; } while(cp < &buf[16]); + (VOID) strcpy(cp, function_name(map->map_default)); + if(addline(bp, buf) == FALSE) return FALSE; + } + last = ele->k_num; + for(i=ele->k_base; i <= last; i++) { + functp = ele->k_funcp[i - ele->k_base]; + if(functp != rescan) { + if(functp != prefix) cp2 = function_name(functp); + else cp2 = map_name(ele->k_prefmap); + if(cp2 != NULL) { + cp = keyname(ind, i); + do { *cp++ = ' '; } while(cp < &buf[16]); + (VOID) strcpy(cp, cp2); + if (addline(bp, buf) == FALSE) return FALSE; + } + } + } + } + for(ele = &map->map_element[0]; ele < &map->map_element[map->map_num]; ele++) { + if(ele->k_prefmap != NULL) { + for(i = ele->k_base; ele->k_funcp[i - ele->k_base] != prefix; i++) { + if(i >= ele->k_num) /* damaged map */ + return FALSE; + } + cp = keyname(ind, i); + *cp++ = ' '; + if(showall(cp, ele->k_prefmap) == FALSE) return FALSE; + } + } + return TRUE; +} + +help_help(f, n) +int f, n; +{ + KEYMAP *kp; + PF funct; + + if((kp = name_map("help")) == NULL) return FALSE; + ewprintf("a b c: "); + do { + funct = doscan(kp, getkey(FALSE)); + } while(funct==NULL || funct==help_help); +#ifndef NO_MACRO + if(macrodef && macrocount < MAXMACRO) macro[macrocount-1].m_funct = funct; +#endif + return (*funct)(f, n); +} + +static char buf2[128]; +static char *buf2p; + +/*ARGSUSED*/ +apropos_command(f, n) +int f, n; +{ + register char *cp1, *cp2; + char string[32]; + FUNCTNAMES *fnp; + BUFFER *bp; + static VOID findbind(); + + if(eread("apropos: ", string, sizeof(string), EFNEW) == ABORT) return ABORT; + /* FALSE means we got a 0 character string, which is fine */ + bp = bfind("*help*", TRUE); + if(bclear(bp) == FALSE) return FALSE; + for(fnp = &functnames[0]; fnp < &functnames[nfunct]; fnp++) { + for(cp1 = fnp->n_name; *cp1; cp1++) { + cp2 = string; + while(*cp2 && *cp1 == *cp2) + cp1++, cp2++; + if(!*cp2) { + (VOID) strcpy(buf2, fnp->n_name); + buf2p = &buf2[strlen(buf2)]; + findbind(fnp->n_funct, buf, map_table[0].p_map); + if(addline(bp, buf2) == FALSE) return FALSE; + break; + } else cp1 -= cp2 - string; + } + } + return popbuftop(bp); +} + +static VOID findbind(funct, ind, map) +PF funct; +char *ind; +KEYMAP *map; +{ + register MAP_ELEMENT *ele; + register int i; + char *cp; + int last; + static VOID bindfound(); + + last = -1; + for(ele = &map->map_element[0]; ele < &map->map_element[map->map_num]; ele++) { + if(map->map_default == funct && ++last < ele->k_base) { + cp = keyname(ind, last); + if(last < ele->k_base - 1) { + (VOID) strcpy(cp, " .. "); + (VOID) keyname(cp + 4, ele->k_base - 1); + } + bindfound(); + } + last = ele->k_num; + for(i=ele->k_base; i <= last; i++) { + if(funct == ele->k_funcp[i - ele->k_base]) { + if(funct == prefix) { + cp = map_name(ele->k_prefmap); + if(!cp || strncmp(cp, buf2, strlen(cp)) != 0) continue; + } + (VOID) keyname(ind, i); + bindfound(); + } + } + } + for(ele = &map->map_element[0]; ele < &map->map_element[map->map_num]; ele++) { + if(ele->k_prefmap != NULL) { + for(i = ele->k_base; ele->k_funcp[i - ele->k_base] != prefix; i++) { + if(i >= ele->k_num) return; /* damaged */ + } + cp = keyname(ind, i); + *cp++ = ' '; + findbind(funct, cp, ele->k_prefmap); + } + } +} + +static VOID bindfound() { + if(buf2p < &buf2[32]) { + do { *buf2p++ = ' '; } while(buf2p < &buf2[32]); + } else { + *buf2p++ = ','; + *buf2p++ = ' '; + } + (VOID) strcpy(buf2p, buf); + buf2p += strlen(buf); +} +#endif diff --git a/usr.bin/mg/kbd.c b/usr.bin/mg/kbd.c new file mode 100644 index 00000000000..3c2dd86da0c --- /dev/null +++ b/usr.bin/mg/kbd.c @@ -0,0 +1,391 @@ +/* + * Terminal independent keyboard handling. + */ +#include "def.h" +#include "kbd.h" + +#define EXTERN +#include "key.h" + +#ifndef NO_MACRO +#include "macro.h" +#endif + +#ifdef DO_METAKEY +#ifndef METABIT +#define METABIT 0x80 +#endif + +int use_metakey = TRUE; + +/* + * Toggle the value of use_metakey + */ +do_meta(f, n) +{ + if(f & FFARG) use_metakey = n > 0; + else use_metakey = !use_metakey; + ewprintf("Meta keys %sabled", use_metakey ? "en" : "dis"); + return TRUE; +} +#endif + +#ifdef BSMAP +static int bs_map = BSMAP; +/* + * Toggle backspace mapping + */ +bsmap(f, n) +{ + if(f & FFARG) bs_map = n > 0; + else bs_map = ! bs_map; + ewprintf("Backspace mapping %sabled", bs_map ? "en" : "dis"); + return TRUE; +} +#endif + +#ifndef NO_DPROMPT +#define PROMPTL 80 + char prompt[PROMPTL], *promptp; +#endif + +static int pushed = FALSE; +static int pushedc; + +VOID ungetkey(c) +int c; +{ +#ifdef DO_METAKEY + if(use_metakey && pushed && c==CCHR('[')) pushedc |= METABIT; + else +#endif + pushedc = c; + pushed = TRUE; +} + +int getkey(flag) +int flag; +{ + int c; + char *keyname(); + +#ifndef NO_DPROMPT + if(flag && !pushed) { + if(prompt[0]!='\0' && ttwait()) { + ewprintf("%s", prompt); /* avoid problems with % */ + update(); /* put the cursor back */ + epresf = KPROMPT; + } + if(promptp > prompt) *(promptp-1) = ' '; + } +#endif + if(pushed) { + c = pushedc; + pushed = FALSE; + } else c = getkbd(); +#ifdef BSMAP + if(bs_map) + if(c==CCHR('H')) c=CCHR('?'); + else if(c==CCHR('?')) c=CCHR('H'); +#endif +#ifdef DO_METAKEY + if(use_metakey && (c&METABIT)) { + pushedc = c & ~METABIT; + pushed = TRUE; + c = CCHR('['); + } +#endif +#ifndef NO_DPROMPT + if(flag && promptp < &prompt[PROMPTL - 5]) { + promptp = keyname(promptp, c); + *promptp++ = '-'; + *promptp = '\0'; + } +#endif + 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. + */ + +MAP_ELEMENT *ele; + +PF doscan(map, c) +register KEYMAP *map; +register int c; +{ + register MAP_ELEMENT *elec = &map->map_element[0]; /* local register copy for faster access */ + register MAP_ELEMENT *last = &map->map_element[map->map_num]; + + while(elec < last && c > elec->k_num) elec++; + ele = elec; /* used by prefix and binding code */ + if(elec >= last || c < elec->k_base) + return map->map_default; + return elec->k_funcp[c - elec->k_base]; +} + +doin() +{ + KEYMAP *curmap; + PF funct; + +#ifndef NO_DPROMPT + *(promptp = prompt) = '\0'; +#endif + 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)))) + == prefix) + curmap = ele->k_prefmap; +#ifndef NO_MACRO + if(macrodef && macrocount < MAXMACRO) + macro[macrocount++].m_funct = funct; +#endif + return (*funct)(0, 1); +} + +rescan(f, n) +int f, n; +{ + int c; + register KEYMAP *curmap; + int i; + PF fp; + int mode = 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[mode]->p_map; + for(i=0; i < key.k_count-1; i++) { + if((fp=doscan(curmap,(key.k_chars[i]))) != prefix) break; + curmap = ele->k_prefmap; + } + if(fp==prefix) { + if((fp = doscan(curmap, c)) == prefix) + while((fp=doscan(curmap,key.k_chars[key.k_count++] = + getkey(TRUE))) == prefix) + curmap = ele->k_prefmap; + if(fp!=rescan) { +#ifndef NO_MACRO + if(macrodef && macrocount <= MAXMACRO) + macro[macrocount-1].m_funct = fp; +#endif + return (*fp)(f, n); + } + } + } + /* try previous mode */ + if(--mode < 0) return ABORT; + curmap = curbp->b_modes[mode]->p_map; + for(i=0; i < key.k_count; i++) { + if((fp=doscan(curmap,(key.k_chars[i]))) != prefix) break; + curmap = ele->k_prefmap; + } + if(fp==prefix) { + while((fp=doscan(curmap,key.k_chars[i++]=getkey(TRUE))) + == prefix) + curmap = ele->k_prefmap; + key.k_count = i; + } + if(fp!=rescan && i>=key.k_count-1) { +#ifndef NO_MACRO + if(macrodef && macrocount <= MAXMACRO) + macro[macrocount-1].m_funct = fp; +#endif + return (*fp)(f, n); + } + } +} + +universal_argument(f, n) +int f, n; +{ + int c, nn=4; + KEYMAP *curmap; + PF funct; + + 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)) == prefix) { + curmap = ele->k_prefmap; + key.k_chars[key.k_count++] = c = getkey(TRUE); + } + if(funct != universal_argument) { +#ifndef NO_MACRO + if(macrodef && macrocount < MAXMACRO-1) { + if(f&FFARG) macrocount--; + macro[macrocount++].m_count = nn; + macro[macrocount++].m_funct = funct; + } +#endif + return (*funct)(FFUNIV, nn); + } + nn <<= 2; + } +} + +/*ARGSUSED*/ +digit_argument(f, n) +int f, n; +{ + int nn, c; + KEYMAP *curmap; + PF funct; + + 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)) == prefix) { + curmap = ele->k_prefmap; + key.k_chars[key.k_count++] = c = getkey(TRUE); + } +#ifndef NO_MACRO + 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; + } +#endif + return (*funct)(FFOTHARG, nn); +} + +negative_argument(f, n) +int f, n; +{ + int nn = 0, c; + KEYMAP *curmap; + PF funct; + + 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)) == prefix) { + curmap = ele->k_prefmap; + key.k_chars[key.k_count++] = c = getkey(TRUE); + } +#ifndef NO_MACRO + 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; + } +#endif + return (*funct)(FFNEGARG, nn); +} + +/* + * Insert a character. While defining a macro, create a "LINE" containing + * all inserted characters. + */ + +selfinsert(f, n) +int f, n; +{ + register int c; + int count; + VOID lchange(); +#ifndef NO_MACRO + LINE *lp; + int insert(); +#endif + + if (n < 0) return FALSE; + if (n == 0) return TRUE; + c = key.k_chars[key.k_count-1]; +#ifndef NO_MACRO + if(macrodef && macrocount < MAXMACRO) { + if(f & FFARG) macrocount -= 2; + if(lastflag & CFINS) { /* last command was insert -- tack on end */ + macrocount--; + if(maclcur->l_size < maclcur->l_used + n) { + if((lp = lallocx(maclcur->l_used + n)) == NULL) + return FALSE; + lp->l_fp = maclcur->l_fp; + lp->l_bp = maclcur->l_bp; + lp->l_fp->l_bp = lp->l_bp->l_fp = lp; + bcopy(maclcur->l_text, lp->l_text, maclcur->l_used); + for(count = maclcur->l_used; count < lp->l_used; count++) + lp->l_text[count] = c; + free((char *)maclcur); + maclcur = lp; + } else { + maclcur->l_used += n; + 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 = lallocx(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; + } +#endif + if(c == '\n') { + do { + count = lnewline(); + } while (--n && count==TRUE); + return count; + } + if(curbp->b_flag & BFOVERWRITE) { /* Overwrite mode */ + 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 everthing defined + * as self-insert. + */ +quote(f, n) +{ + register 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); +} diff --git a/usr.bin/mg/kbd.h b/usr.bin/mg/kbd.h new file mode 100644 index 00000000000..1816fc6462c --- /dev/null +++ b/usr.bin/mg/kbd.h @@ -0,0 +1,63 @@ +/* + * kbd.h: type definitions for symbol.c and kbd.c for mg experimental + */ + +typedef struct { + 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 */ +} MAP_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;\ + short map_max;\ + PF map_default;\ + MAP_ELEMENT map_element[NUM];\ +} + /* elements used */ + /* elements allocated */ + /* default function */ + /* realy [e_max] */ +typedef struct keymap_s KEYMAPE(1) KEYMAP; + +#define none ctrlg +#define prefix (PF)NULL + +/* 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 + +typedef struct MAPS_S { + KEYMAP *p_map; + char *p_name; +} MAPS; + +extern MAPS map_table[]; + +typedef struct { + PF n_funct; + char *n_name; +} FUNCTNAMES; + +extern FUNCTNAMES functnames[]; +extern int nfunct; + +extern PF doscan(); +extern PF name_function(); +extern char *function_name(); +extern int complete_function(); +extern KEYMAP *name_map(); +extern char *map_name(); +extern MAPS *name_mode(); + +extern MAP_ELEMENT *ele; diff --git a/usr.bin/mg/key.h b/usr.bin/mg/key.h new file mode 100644 index 00000000000..88e8bc1f1c7 --- /dev/null +++ b/usr.bin/mg/key.h @@ -0,0 +1,13 @@ +/* key.h: Insert file for mg 2 functions that need to reference key pressed */ + +#ifndef EXTERN +#define EXTERN extern +#endif + +#define MAXKEY 8 /* maximum number of prefix chars */ + +EXTERN struct { /* the chacter sequence in a key */ + int k_count; /* number of chars */ + KCHAR k_chars[MAXKEY]; /* chars */ +} key; +#undef EXTERN diff --git a/usr.bin/mg/keymap.c b/usr.bin/mg/keymap.c new file mode 100644 index 00000000000..d880bce016d --- /dev/null +++ b/usr.bin/mg/keymap.c @@ -0,0 +1,1217 @@ +/* + * 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" + +/* + * Defined by "basic.c". + */ +extern int gotobol(); /* Move to start of line */ +extern int backchar(); /* Move backward by characters */ +extern int gotoeol(); /* Move to end of line */ +extern int forwchar(); /* Move forward by characters */ +extern int gotobob(); /* Move to start of buffer */ +extern int gotoeob(); /* Move to end of buffer */ +extern int forwline(); /* Move forward by lines */ +extern int backline(); /* Move backward by lines */ +extern int forwpage(); /* Move forward by pages */ +extern int backpage(); /* Move backward by pages */ +extern int pagenext(); /* Page forward next window */ +extern int setmark(); /* Set mark */ +extern int swapmark(); /* Swap "." and mark */ +extern int gotoline(); /* Go to a specified line. */ +#ifdef GOSMACS +extern int forw1page(); /* move forward by lines */ +extern int back1page(); /* move back by lines */ +#endif + +/* + * Defined by "buffer.c". + */ +extern int listbuffers(); /* Display list of buffers */ +extern int usebuffer(); /* Switch a window to a buffer */ +extern int poptobuffer(); /* Other window to a buffer */ +extern int killbuffer(); /* Make a buffer go away. */ +extern int savebuffers(); /* Save unmodified buffers */ +extern int bufferinsert(); /* Insert buffer into another */ +extern int notmodified(); /* Reset modification flag */ + +#ifndef NO_DIR +/* + * Defined by "dir.c" + */ +extern int changedir(); /* change current directory */ +extern int showcwdir(); /* show current directory */ + +#ifndef NO_DIRED +/* + * defined by "dired.c" + */ +extern int dired(); /* dired */ +extern int d_findfile(); /* dired find file */ +extern int d_del(); /* dired mark for deletion */ +extern int d_undel(); /* dired unmark */ +extern int d_undelbak(); /* dired unmark backwards */ +extern int d_expunge(); /* dired expunge */ +extern int d_copy(); /* dired copy */ +extern int d_rename(); /* dired rename */ +extern int d_otherwindow(); /* dired other window */ +extern int d_ffotherwindow(); /* dired find file other window */ +#endif +#endif + +/* + * Defined by "extend.c". + */ +extern int extend(); /* Extended commands. */ +extern int bindtokey(); /* Modify global key bindings. */ +extern int localbind(); /* Modify mode key bindings. */ +extern int define_key(); /* modify any key map */ +extern int unbindtokey(); /* delete global binding */ +extern int localunbind(); /* delete local binding */ +extern int insert(); /* insert string */ +#ifndef NO_STARTUP +extern int evalexpr(); /* Extended commands (again) */ +extern int evalbuffer(); /* Evaluate current buffer */ +extern int evalfile(); /* Evaluate a file */ +#endif + +/* + * Defined by "file.c". + */ +extern int filevisit(); /* Get a file, read write */ +extern int poptofile(); /* Get a file, other window */ +extern int filewrite(); /* Write a file */ +extern int filesave(); /* Save current file */ +extern int fileinsert(); /* Insert file into buffer */ +#ifndef NO_BACKUP +extern int makebkfile(); /* Control backups on saves */ +#endif + +/* + * defined by help.c + */ +#ifndef NO_HELP +extern int desckey(); /* describe key */ +extern int wallchart(); /* Make wall chart. */ +extern int help_help(); /* help help */ +extern int apropos_command(); /* apropos */ +#endif + +/* + * defined by "kbd.c" + */ +#ifdef DO_METAKEY +extern int do_meta(); /* interpret meta keys */ +#endif +#ifdef BSMAP +extern int bsmap(); /* backspace mapping */ +#endif +extern int universal_argument(); /* Ctrl-U */ +extern int digit_argument(); /* M-1, etc. */ +extern int negative_argument(); /* M-- */ +extern int selfinsert(); /* Insert character */ +extern int rescan(); /* internal try again function */ + +/* + * defined by "macro.c" + */ +#ifndef NO_MACRO +extern int definemacro(); /* Begin macro */ +extern int finishmacro(); /* End macro */ +extern int executemacro(); /* Execute macro */ +#endif + +/* + * Defined by "main.c". + */ +extern int ctrlg(); /* Abort out of things */ +extern int quit(); /* Quit */ + +/* + * Defined by "match.c" + */ +extern int showmatch(); /* Hack to show matching paren */ + +/* defined by "modes.c" */ + +extern int indentmode(); /* set auto-indent mode */ +extern int fillmode(); /* set word-wrap mode */ +extern int blinkparen(); /* Fake blink-matching-paren var */ +#ifdef NOTAB +extern int notabmode(); /* no tab mode */ +#endif +extern int overwrite(); /* overwrite mode */ +extern int set_default_mode(); /* set default modes */ + +/* + * defined by "paragraph.c" - the paragraph justification code. + */ +extern int gotobop(); /* Move to start of paragraph. */ +extern int gotoeop(); /* Move to end of paragraph. */ +extern int fillpara(); /* Justify a paragraph. */ +extern int killpara(); /* Delete a paragraph. */ +extern int setfillcol(); /* Set fill column for justify. */ +extern int fillword(); /* Insert char with word wrap. */ + +/* + * Defined by "random.c". + */ +extern int showcpos(); /* Show the cursor position */ +extern int twiddle(); /* Twiddle characters */ +extern int quote(); /* Insert literal */ +extern int openline(); /* Open up a blank line */ +extern int newline(); /* Insert newline */ +extern int deblank(); /* Delete blank lines */ +extern int justone(); /* Delete extra whitespace */ +extern int delwhite(); /* Delete all whitespace */ +extern int indent(); /* Insert newline, then indent */ +extern int forwdel(); /* Forward delete */ +extern int backdel(); /* Backward delete in */ +extern int killline(); /* Kill forward */ +extern int yank(); /* Yank back from killbuffer. */ +#ifdef NOTAB +extern int space_to_tabstop(); +#endif + +#ifdef REGEX +/* + * Defined by "re_search.c" + */ +extern int re_forwsearch(); /* Regex search forward */ +extern int re_backsearch(); /* Regex search backwards */ +extern int re_searchagain(); /* Repeat regex search command */ +extern int re_queryrepl(); /* Regex query replace */ +extern int setcasefold(); /* Set case fold in searches */ +extern int delmatchlines(); /* Delete all lines matching */ +extern int delnonmatchlines(); /* Delete all lines not matching */ +extern int cntmatchlines(); /* Count matching lines */ +extern int cntnonmatchlines(); /* Count nonmatching lines */ +#endif + +/* + * Defined by "region.c". + */ +extern int killregion(); /* Kill region. */ +extern int copyregion(); /* Copy region to kill buffer. */ +extern int lowerregion(); /* Lower case region. */ +extern int upperregion(); /* Upper case region. */ +#ifdef PREFIXREGION +extern int prefixregion(); /* Prefix all lines in region */ +extern int setprefix(); /* Set line prefix string */ +#endif + +/* + * Defined by "search.c". + */ +extern int forwsearch(); /* Search forward */ +extern int backsearch(); /* Search backwards */ +extern int searchagain(); /* Repeat last search command */ +extern int forwisearch(); /* Incremental search forward */ +extern int backisearch(); /* Incremental search backwards */ +extern int queryrepl(); /* Query replace */ + +/* + * Defined by "spawn.c". + */ +extern int spawncli(); /* Run CLI in a subjob. */ +extern int attachtoparent(); /* Attach to parent process */ + +/* defined by "version.c" */ + +extern int showversion(); /* Show version numbers, etc. */ + +/* + * Defined by "window.c". + */ +extern int reposition(); /* Reposition window */ +extern int refresh(); /* Refresh the screen */ +extern int nextwind(); /* Move to the next window */ +#ifdef GOSMACS +extern int prevwind(); /* Move to the previous window */ +#endif +extern int onlywind(); /* Make current window only one */ +extern int splitwind(); /* Split current window */ +extern int delwind(); /* Delete current window */ +extern int enlargewind(); /* Enlarge display window. */ +extern int shrinkwind(); /* Shrink window. */ + +/* + * Defined by "word.c". + */ +extern int backword(); /* Backup by words */ +extern int forwword(); /* Advance by words */ +extern int upperword(); /* Upper case word. */ +extern int lowerword(); /* Lower case word. */ +extern int capword(); /* Initial capitalize word. */ +extern int delfword(); /* Delete forward word. */ +extern int delbword(); /* Delete backward word. */ + +#ifdef AMIGA +#ifdef DO_ICONIFY +extern int tticon(); +#endif +#ifdef DO_MENU +extern int amigamenu(); /* Menu function */ +#endif +#ifdef MOUSE +extern int amigamouse(); /* Amiga mouse functions */ +extern int mgotobob(); +extern int mforwdel(); +extern int mdelwhite(); +extern int mdelwind(); +extern int mgotoeob(); +extern int menlargewind(); +extern int mkillline(); +extern int mkillregion(); +extern int mdelfword(); +extern int mreposition(); +extern int mbackpage(); +extern int mforwpage(); +extern int mshrinkwind(); +extern int msplitwind(); +extern int myank(); +#endif MOUSE + +extern int togglewindow(); /* Defined by "ttyio.c" */ +extern int togglezooms(); /* "" "" */ + +#ifdef CHANGE_FONT +extern int setfont(); /* Defined by "ttyio.c" */ +#endif + +#ifdef CHANGE_COLOR + /* functions to mess with the mode line rendition, window colors*/ +extern int ttmode(); /* Defined by "tty.c" */ +extern int tttext(); /* "" */ +extern int textforeground(); /* "" */ +extern int textbackground(); /* "" */ +extern int modeforeground(); /* "" */ +extern int modebackground(); /* "" */ +#endif + +/* + * This file contains map segment definitions for adding function keys to + * keymap declarations. Currently you can add things to the fundamental + * mode keymap and the dired mode keymap. See the declaration of + * diredmap and fundmap for details. + */ +#include "amiga_maps.c" + +#endif /* AMIGA */ + +/* initial keymap declarations, deepest first */ + +#ifndef NO_HELP +static PF cHcG[] = { + ctrlg, /* ^G */ + help_help, /* ^H */ +}; +static PF cHa[] = { + apropos_command,/* a */ + wallchart, /* b */ + desckey, /* c */ +}; +static struct KEYMAPE(2+IMAPEXT) helpmap = { + 2, + 2+IMAPEXT, + rescan, + { + {CCHR('G'),CCHR('H'), cHcG, (KEYMAP *)NULL}, + {'a', 'c', cHa, (KEYMAP *)NULL}, + } +}; +#endif + +static struct KEYMAPE(1+IMAPEXT) extramap1 = { + 0, + 1+IMAPEXT, + rescan +}; + +static struct KEYMAPE(1+IMAPEXT) extramap2 = { + 0, + 1+IMAPEXT, + rescan +}; + +static struct KEYMAPE(1+IMAPEXT) extramap3 = { + 0, + 1+IMAPEXT, + rescan +}; + +static struct KEYMAPE(1+IMAPEXT) extramap4 = { + 0, + 1+IMAPEXT, + rescan +}; + +static struct KEYMAPE(1+IMAPEXT) extramap5 = { + 0, + 1+IMAPEXT, + rescan +}; + +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, (KEYMAP *)NULL}, + {'b', 'f', cX4b, (KEYMAP *)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 */ + rescan, /* ^Q */ + rescan, /* ^R */ + filesave, /* ^S */ + rescan, /* ^T */ + upperregion, /* ^U */ + rescan, /* ^V */ + filewrite, /* ^W */ + swapmark, /* ^X */ +}; +#ifndef NO_MACRO +static PF cXlp[] = { + definemacro, /* ( */ + finishmacro, /* ) */ +}; +#endif +static PF cX0[] = { + delwind, /* 0 */ + onlywind, /* 1 */ + splitwind, /* 2 */ + rescan, /* 3 */ + prefix, /* 4 */ +}; +static PF cXeq[] = { + showcpos, /* = */ +}; +static PF cXcar[] = { + enlargewind, /* ^ */ + rescan, /* _ */ + rescan, /* ` */ + rescan, /* a */ + usebuffer, /* b */ + rescan, /* c */ +#ifndef NO_DIRED + dired, /* d */ +#else + rescan, /* d */ +#endif +#ifndef NO_MACRO + executemacro, /* e */ +#else + rescan, /* e */ +#endif + setfillcol, /* f */ + rescan, /* g */ + rescan, /* h */ + fileinsert, /* i */ + rescan, /* j */ + killbuffer, /* k */ + rescan, /* l */ + rescan, /* m */ + rescan, /* n */ + nextwind, /* o */ + rescan, /* p */ + rescan, /* q */ + rescan, /* r */ + savebuffers, /* s */ +}; +#ifndef NO_MACRO +static struct KEYMAPE(6+IMAPEXT) cXmap = { + 6, + 6+IMAPEXT, +#else +static struct KEYMAPE(5+IMAPEXT) cXmap = { + 5, + 5+IMAPEXT, +#endif + rescan, + { + {CCHR('B'),CCHR('G'), cXcB, (KEYMAP *)NULL}, + {CCHR('L'),CCHR('X'), cXcL, (KEYMAP *)NULL}, +#ifndef NO_MACRO + {'(', ')', cXlp, (KEYMAP *)NULL}, +#endif + {'0', '4', cX0, (KEYMAP *)&cX4map}, + {'=', '=', cXeq, (KEYMAP *)NULL}, + {'^', 's', cXcar, (KEYMAP *)NULL}, + } +}; + +static PF metacG[] = { + ctrlg, /* ^G */ +}; +static PF metacV[] = { + pagenext, /* ^V */ +}; +static PF metasp[] = { + justone, /* space */ +}; +static PF metapct[] = { + queryrepl, /* % */ +}; +static PF metami[] = { + negative_argument, /* - */ + rescan, /* . */ + 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 metalb[] = { + gotobop, /* [ */ + delwhite, /* \ */ + gotoeop, /* ] */ + rescan, /* ^ */ + rescan, /* _ */ + rescan, /* ` */ + rescan, /* a */ + backword, /* b */ + capword, /* c */ + delfword, /* d */ + rescan, /* e */ + forwword, /* f */ +}; +static PF metal[] = { + lowerword, /* l */ + rescan, /* m */ + rescan, /* n */ + rescan, /* o */ + rescan, /* p */ + fillpara, /* q */ + backsearch, /* r */ + forwsearch, /* s */ + rescan, /* t */ + upperword, /* u */ + backpage, /* v */ + copyregion, /* w */ + extend, /* x */ +}; +static PF metatilde[] = { + notmodified, /* ~ */ + delbword, /* DEL */ +}; +static struct KEYMAPE(8+IMAPEXT) metamap = { + 8, + 8+IMAPEXT, + rescan, + { + {CCHR('G'),CCHR('G'), metacG, (KEYMAP *)NULL}, + {CCHR('V'),CCHR('V'), metacV, (KEYMAP *)NULL}, + {' ', ' ', metasp, (KEYMAP *)NULL}, + {'%', '%', metapct,(KEYMAP *)NULL}, + {'-', '>', metami, (KEYMAP *)NULL}, + {'[', 'f', metalb, (KEYMAP *)NULL}, + {'l', 'x', metal, (KEYMAP *)NULL}, + {'~', CCHR('?'), metatilde,(KEYMAP *)NULL}, + } +}; + +static PF fund_at[] = { + setmark, /* ^@ */ + gotobol, /* ^A */ + backchar, /* ^B */ + rescan, /* ^C */ + forwdel, /* ^D */ + gotoeol, /* ^E */ + forwchar, /* ^F */ + ctrlg, /* ^G */ +#ifndef NO_HELP + prefix, /* ^H */ +#else + rescan, /* ^H */ +#endif +}; +/* ^I is selfinsert */ +static PF fund_CJ[] = { + indent, /* ^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 */ + prefix, /* ^X */ + yank, /* ^Y */ + attachtoparent, /* ^Z */ +}; +static PF fund_esc[] = { + prefix, /* esc */ + rescan, /* ^\ */ /* selfinsert is default on fundamental */ + rescan, /* ^] */ + rescan, /* ^^ */ + rescan, /* ^_ */ +}; +static PF fund_del[] = { + backdel, /* DEL */ +}; + +#ifndef FUND_XMAPS +#define NFUND_XMAPS 0 /* extra map sections after normal ones */ +#endif + +static struct KEYMAPE(4+NFUND_XMAPS+IMAPEXT) fundmap = { + 4 + NFUND_XMAPS, + 4 + NFUND_XMAPS + IMAPEXT, + selfinsert, + { +#ifndef NO_HELP + {CCHR('@'),CCHR('H'), fund_at, (KEYMAP *)&helpmap}, +#else + {CCHR('@'),CCHR('H'), fund_at, (KEYMAP *)NULL}, +#endif + {CCHR('J'),CCHR('Z'), fund_CJ, (KEYMAP *)&cXmap}, + {CCHR('['),CCHR('_'), fund_esc,(KEYMAP *)&metamap}, + {CCHR('?'),CCHR('?'), fund_del,(KEYMAP *)NULL}, +#ifdef FUND_XMAPS + FUND_XMAPS, +#endif + } +}; + +static PF fill_sp[] = { + fillword, /* ' ' */ +}; +static struct KEYMAPE(1+IMAPEXT) fillmap = { + 1, + 1+IMAPEXT, + rescan, + { + {' ', ' ', fill_sp, (KEYMAP *)NULL}, + } +}; + +static PF indent_lf[] = { + newline, /* ^J */ + rescan, /* ^K */ + rescan, /* ^L */ + indent, /* ^M */ +}; +static struct KEYMAPE(1+IMAPEXT) indntmap = { + 1, + 1+IMAPEXT, + rescan, + { + {CCHR('J'), CCHR('M'), indent_lf, (KEYMAP *)NULL}, + } +}; +static PF blink_rp[] = { + showmatch, /* ) */ +}; +static struct KEYMAPE(1+IMAPEXT) blinkmap = { + 1, + 1+IMAPEXT, + rescan, + { + {')', ')', blink_rp, (KEYMAP *)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, (KEYMAP *)NULL}, + } +}; +#endif + +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, (PF *)NULL, (KEYMAP *)NULL}, + } +}; + +#ifndef NO_DIRED +static PF dirednul[] = { + setmark, /* ^@ */ + gotobol, /* ^A */ + backchar, /* ^B */ + rescan, /* ^C */ + d_del, /* ^D */ + gotoeol, /* ^E */ + forwchar, /* ^F */ + ctrlg, /* ^G */ +#ifndef NO_HELP + prefix, /* ^H */ +#endif +}; +static PF diredcl[] = { + reposition, /* ^L */ + forwline, /* ^M */ + forwline, /* ^N */ + rescan, /* ^O */ + backline, /* ^P */ + rescan, /* ^Q */ + backisearch, /* ^R */ + forwisearch, /* ^S */ + rescan, /* ^T */ + universal_argument, /* ^U */ + forwpage, /* ^V */ + rescan, /* ^W */ + prefix, /* ^X */ +}; +static PF diredcz[] = { + attachtoparent, /* ^Z */ + prefix, /* esc */ + rescan, /* ^\ */ + rescan, /* ^] */ + rescan, /* ^^ */ + rescan, /* ^_ */ + forwline, /* SP */ +}; +static PF diredc[] = { + d_copy, /* c */ + d_del, /* d */ + d_findfile, /* e */ + d_findfile, /* f */ +}; +static PF diredn[] = { + forwline, /* n */ + d_ffotherwindow,/* o */ + 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 */ +}; + +#ifndef DIRED_XMAPS +#define NDIRED_XMAPS 0 /* number of extra map sections */ +#endif + +static struct KEYMAPE(6 + NDIRED_XMAPS + IMAPEXT) diredmap = { + 6 + NDIRED_XMAPS, + 6 + NDIRED_XMAPS + IMAPEXT, + rescan, + { +#ifndef NO_HELP + {CCHR('@'), CCHR('H'), dirednul, (KEYMAP *)&helpmap}, +#else + {CCHR('@'), CCHR('G'), dirednul, (KEYMAP *)NULL}, +#endif + {CCHR('L'), CCHR('X'), diredcl, (KEYMAP *)&cXmap}, + {CCHR('Z'), ' ', diredcz, (KEYMAP *)&metamap}, + {'c', 'f', diredc, (KEYMAP *)NULL}, + {'n', 'x', diredn, (KEYMAP *)NULL}, + {CCHR('?'), CCHR('?'), direddl, (KEYMAP *)NULL}, +#ifdef DIRED_XMAPS + DIRED_XMAPS, /* map sections for dired mode keys */ +#endif + } +}; +#endif + +/* 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 dynamicly 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. + */ + +MAPS map_table[] = { + /* fundamental map MUST be first entry */ + {(KEYMAP *)&fundmap, "fundamental"}, + {(KEYMAP *)&fillmap, "fill"}, + {(KEYMAP *)&indntmap, "indent"}, + {(KEYMAP *)&blinkmap, "blink"}, +#ifdef NOTAB + {(KEYMAP *)¬abmap, "notab"}, +#endif + {(KEYMAP *)&overwmap, "overwrite"}, + {(KEYMAP *)&metamap, "esc prefix"}, + {(KEYMAP *)&cXmap, "c-x prefix"}, + {(KEYMAP *)&cX4map, "c-x 4 prefix"}, + {(KEYMAP *)&extramap1, "extra prefix 1"}, + {(KEYMAP *)&extramap2, "extra prefix 2"}, + {(KEYMAP *)&extramap3, "extra prefix 3"}, + {(KEYMAP *)&extramap4, "extra prefix 4"}, + {(KEYMAP *)&extramap5, "extra prefix 5"}, +#ifndef NO_HELP + {(KEYMAP *)&helpmap, "help"}, +#endif +#ifndef NO_DIRED + {(KEYMAP *)&diredmap, "dired"}, +#endif +}; + +#define NMAPS (sizeof map_table/sizeof(MAPS)) +int nmaps = NMAPS; /* for use by rebind in extend.c */ + +char *map_name(map) +KEYMAP *map; +{ + MAPS *mp = &map_table[0]; + + do { + if(mp->p_map == map) return mp->p_name; + } while(++mp < &map_table[NMAPS]); + return (char *)NULL; +} + +MAPS *name_mode(name) +char *name; +{ + MAPS *mp = &map_table[0]; + + do { + if(strcmp(mp->p_name,name)==0) return mp; + } while(++mp < &map_table[NMAPS]); + return (MAPS *)NULL; +} + +KEYMAP *name_map(name) +char *name; +{ + MAPS *mp; + return (mp=name_mode(name))==NULL ? (KEYMAP *)NULL : mp->p_map; +} + +/* Warning: functnames MUST be in alphabetical order! (due to binary + * search in name_function.) If the function is prefix, it must be listed + * with the same name in the map_table above. + */ + +FUNCTNAMES functnames[] = { +#ifdef AMIGA +#ifdef DO_ICONIFY + {tticon, "amiga-iconify"}, +#endif +#ifdef DO_MENU + {amigamenu, "amiga-menu"}, +#endif +#ifdef CHANGE_COLOR + {modebackground,"amiga-mode-background"}, + {modeforeground,"amiga-mode-foreground"}, + {ttmode, "amiga-mode-rendition"}, +#endif +#ifdef CHANGE_FONT + {setfont, "amiga-set-font"}, +#endif +#ifdef CHANGE_COLOR + {textbackground,"amiga-text-background"}, + {textforeground,"amiga-text-foreground"}, + {tttext, "amiga-text-rendition"}, +#endif + {togglewindow, "amiga-toggle-border"}, + {togglezooms, "amiga-zoom-mode"}, +#endif /* AMIGA */ +#ifndef NO_HELP + {apropos_command, "apropos"}, +#endif + {fillmode, "auto-fill-mode"}, + {indentmode, "auto-indent-mode"}, + {backchar, "backward-char"}, + {delbword, "backward-kill-word"}, + {gotobop, "backward-paragraph"}, + {backword, "backward-word"}, + {gotobob, "beginning-of-buffer"}, + {gotobol, "beginning-of-line"}, + {blinkparen, "blink-matching-paren"}, + {showmatch, "blink-matching-paren-hack"}, +#ifdef BSMAP + {bsmap, "bsmap-mode"}, +#endif + {prefix, "c-x 4 prefix"}, + {prefix, "c-x prefix"}, +#ifndef NO_MACRO + {executemacro, "call-last-kbd-macro"}, +#endif + {capword, "capitalize-word"}, +#ifndef NO_DIR + {changedir, "cd"}, +#endif + {copyregion, "copy-region-as-kill"}, +#ifdef REGEX + {cntmatchlines, "count-matches"}, + {cntnonmatchlines,"count-non-matches"}, +#endif + {define_key, "define-key"}, + {backdel, "delete-backward-char"}, + {deblank, "delete-blank-lines"}, + {forwdel, "delete-char"}, + {delwhite, "delete-horizontal-space"}, +#ifdef REGEX + {delmatchlines, "delete-matching-lines"}, + {delnonmatchlines,"delete-non-matching-lines"}, +#endif + {onlywind, "delete-other-windows"}, + {delwind, "delete-window"}, +#ifndef NO_HELP + {wallchart, "describe-bindings"}, + {desckey, "describe-key-briefly"}, +#endif + {digit_argument,"digit-argument"}, +#ifndef NO_DIRED + {dired, "dired"}, + {d_undelbak, "dired-backup-unflag"}, + {d_copy, "dired-copy-file"}, + {d_expunge, "dired-do-deletions"}, + {d_findfile, "dired-find-file"}, + {d_ffotherwindow, "dired-find-file-other-window"}, + {d_del, "dired-flag-file-deleted"}, + {d_otherwindow, "dired-other-window"}, + {d_rename, "dired-rename-file"}, + {d_undel, "dired-unflag"}, +#endif + {lowerregion, "downcase-region"}, + {lowerword, "downcase-word"}, + {showversion, "emacs-version"}, +#ifndef NO_MACRO + {finishmacro, "end-kbd-macro"}, +#endif + {gotoeob, "end-of-buffer"}, + {gotoeol, "end-of-line"}, + {enlargewind, "enlarge-window"}, + {prefix, "esc prefix"}, +#ifndef NO_STARTUP + {evalbuffer, "eval-current-buffer"}, + {evalexpr, "eval-expression"}, +#endif + {swapmark, "exchange-point-and-mark"}, + {extend, "execute-extended-command"}, + {prefix, "extra prefix 1"}, + {prefix, "extra prefix 2"}, + {prefix, "extra prefix 3"}, + {prefix, "extra prefix 4"}, + {prefix, "extra prefix 5"}, + {fillpara, "fill-paragraph"}, + {filevisit, "find-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"}, +#ifndef NO_HELP + {prefix, "help"}, + {help_help, "help-help"}, +#endif + {insert, "insert"}, + {bufferinsert, "insert-buffer"}, + {fileinsert, "insert-file"}, + {fillword, "insert-with-wrap"}, + {backisearch, "isearch-backward"}, + {forwisearch, "isearch-forward"}, + {justone, "just-one-space"}, + {ctrlg, "keyboard-quit"}, + {killbuffer, "kill-buffer"}, + {killline, "kill-line"}, + {killpara, "kill-paragraph"}, + {killregion, "kill-region"}, + {delfword, "kill-word"}, + {listbuffers, "list-buffers"}, +#ifndef NO_STARTUP + {evalfile, "load"}, +#endif + {localbind, "local-set-key"}, + {localunbind, "local-unset-key"}, +#ifndef NO_BACKUP + {makebkfile, "make-backup-files"}, +#endif +#ifdef DO_METAKEY + {do_meta, "meta-key-mode"}, /* better name, anyone? */ +#endif +#ifdef AMIGA +#ifdef MOUSE + {mgotobob, "mouse-beginning-of-buffer"}, + {mforwdel, "mouse-delete-char"}, + {mdelwhite, "mouse-delete-horizontal-space"}, + {mdelwind, "mouse-delete-window"}, + {mgotoeob, "mouse-end-of-buffer"}, + {menlargewind, "mouse-enlarge-window"}, + {mkillline, "mouse-kill-line"}, + {mkillregion, "mouse-kill-region"}, + {mdelfword, "mouse-kill-word"}, + {mreposition, "mouse-recenter"}, + {mbackpage, "mouse-scroll-down"}, + {mforwpage, "mouse-scroll-up"}, + {amigamouse, "mouse-set-point"}, + {mshrinkwind, "mouse-shrink-window"}, + {msplitwind, "mouse-split-window-vertically"}, + {myank, "mouse-yank"}, +#endif +#endif + {negative_argument, "negative-argument"}, + {newline, "newline"}, + {indent, "newline-and-indent"}, + {forwline, "next-line"}, +#ifdef NOTAB + {notabmode, "no-tab-mode"}, +#endif + {notmodified, "not-modified"}, + {openline, "open-line"}, + {nextwind, "other-window"}, + {overwrite, "overwrite-mode"}, +#ifdef PREFIXREGION + {prefixregion, "prefix-region"}, +#endif + {backline, "previous-line"}, +#ifdef GOSMACS + {prevwind, "previous-window"}, +#endif +#ifdef VMS + {spawncli, "push-to-dcl"}, +#else + {spawncli, "push-shell"}, +#endif +#ifndef NO_DIR + {showcwdir, "pwd"}, +#endif + {queryrepl, "query-replace"}, +#ifdef REGEX + {re_queryrepl, "query-replace-regexp"}, +#endif + {quote, "quoted-insert"}, +#ifdef REGEX + {re_searchagain,"re-search-again"}, + {re_backsearch, "re-search-backward"}, + {re_forwsearch, "re-search-forward"}, +#endif + {reposition, "recenter"}, + {refresh, "redraw-display"}, + {filesave, "save-buffer"}, + {quit, "save-buffers-kill-emacs"}, + {savebuffers, "save-some-buffers"}, + {backpage, "scroll-down"}, +#ifdef GOSMACS + {back1page, "scroll-one-line-down"}, + {forw1page, "scroll-one-line-up"}, +#endif + {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 + {set_default_mode, "set-default-mode"}, + {setfillcol, "set-fill-column"}, + {setmark, "set-mark-command"}, +#ifdef PREFIXREGION + {setprefix, "set-prefix-string"}, +#endif + {shrinkwind, "shrink-window"}, +#ifdef NOTAB + {space_to_tabstop, "space-to-tabstop"}, +#endif + {splitwind, "split-window-vertically"}, +#ifndef NO_MACRO + {definemacro, "start-kbd-macro"}, +#endif + {attachtoparent,"suspend-emacs"}, + {usebuffer, "switch-to-buffer"}, + {poptobuffer, "switch-to-buffer-other-window"}, + {twiddle, "transpose-chars"}, + {universal_argument, "universal-argument"}, + {upperregion, "upcase-region"}, + {upperword, "upcase-word"}, + {showcpos, "what-cursor-position"}, + {filewrite, "write-file"}, + {yank, "yank"}, +}; + +#define NFUNCT (sizeof(functnames)/sizeof(FUNCTNAMES)) + +int nfunct = NFUNCT; /* used by help.c */ + +/* + * The general-purpose version of ROUND2 blows osk C (2.0) out of the water. + * (reboot required) If you need to build a version of mg with less than 32 + * or more than 511 functions, something better must be done. + * The version that should work, but doesn't is: + * #define ROUND2(x) (1+((x>>1)|(x>>2)|(x>>3)|(x>>4)|(x>>5)|(x>>6)|(x>>7)|\ + * (x>>8)|(x>>9)|(x>>10)|(x>>11)|(x>>12)|(x>>13)|(x>>14)|(x>>15))) + */ +#define ROUND2(x) (x<128?(x<64?32:64):(x<256?128:256)) + +static name_fent(fname, flag) +register char *fname; +int flag; +{ + register int try; + register int x = ROUND2(NFUNCT); + register int base = 0; + register int notit; + + do { + /* + can be used instead of | here if more efficent. */ + if((try = base | x) < NFUNCT) { + if((notit = strcmp(fname, functnames[try].n_name)) >= 0) { + if(!notit) return try; + base = try; + } + } + } while((x>>=1) || (try==1 && base==0)); /* try 0 once if needed */ + return flag ? base : -1; +} + +/* + * Translate from function name to function pointer, using binary search. + */ + +PF name_function(fname) +char *fname; +{ + int i; + if((i = name_fent(fname, FALSE)) >= 0) return functnames[i].n_funct; + return (PF)NULL; +} + +/* complete function name */ + +complete_function(fname, c) +register char *fname; +{ + register int i, j, k, l; + int oj; + + i = name_fent(fname, TRUE); + for(j=0; (l=fname[j]) && functnames[i].n_name[j]==l; j++) {} + if(fname[j]!='\0') { + if(++i >= NFUNCT) return -2; /* no match */ + for(j=0; (l=fname[j]) && functnames[i].n_name[j]==l; j++) {} + if(fname[j]!='\0') return -2; /* no match */ + } + if(c==CCHR('M') && functnames[i].n_name[j]=='\0') return -1; + for(k=i+1; k<NFUNCT; k++) { /* find last match */ + for(l=0; functnames[k].n_name[l]==fname[l]; l++) {} + if(l<j) break; + } + k--; + oj = j; + if(k>i) { /* multiple matches */ + while((l = functnames[i].n_name[j]) == functnames[k].n_name[j]) { + fname[j++] = l; + if(l=='-' && c==' ') break; + } + if(j==oj) return -3; /* ambiguous */ + } else { /* single match */ + while(l = functnames[i].n_name[j]) { + fname[j++] = l; + if(l=='-' && c==' ') break; + } + } + fname[j] = '\0'; + return j - oj; +} + +/* list possible function name completions */ + +LIST *complete_function_list(fname, c) +register char *fname; +{ + register int i, j, k, l; + int oj; + LIST *current,*last; + + i = name_fent(fname, TRUE); + for(j=0; (l=fname[j]) && functnames[i].n_name[j]==l; j++) {} + if(fname[j]!='\0') { + if(++i >= NFUNCT) return NULL; /* no match */ + for(j=0; (l=fname[j]) && functnames[i].n_name[j]==l; j++) {} + if(fname[j]!='\0') return NULL; /* no match */ + } +/* + * if(c==CCHR('M') && functnames[i].n_name[j]=='\0') return -1; + */ + for(k=i+1; k<NFUNCT; k++) { /* find last match */ + for(l=0; functnames[k].n_name[l]==fname[l]; l++) {} + if(l<j) break; + } + k--; + last = NULL; + for (; k >= i; k--) { + current = (LIST *)malloc(sizeof(LIST)); + current->l_next = last; + current->l_name = functnames[k].n_name; + last = current; + } + return(last); +} + +/* translate from function pointer to function name. */ + +char *function_name(fpoint) +register PF fpoint; +{ + register FUNCTNAMES *fnp = &functnames[0]; + + if(fpoint == prefix) return (char *)NULL; /* ambiguous */ + do { + if(fnp->n_funct == fpoint) return fnp->n_name; + } while(++fnp < &functnames[NFUNCT]); + return (char *)NULL; +} diff --git a/usr.bin/mg/line.c b/usr.bin/mg/line.c new file mode 100644 index 00000000000..3c892c706b1 --- /dev/null +++ b/usr.bin/mg/line.c @@ -0,0 +1,609 @@ +/* + * 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 being 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" + +/* number of bytes member is from start of structure type */ +/* should be computed at compile time */ + +#ifndef OFFSET +#define OFFSET(type,member) ((char *)&(((type *)0)->member)-(char *)((type *)0)) +#endif + +#ifndef NBLOCK +#define NBLOCK 16 /* Line block chunk size */ +#endif + +#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. */ + +/* + * This routine allocates a block of memory large enough to hold a LINE + * containing "used" characters. The block is rounded up to whatever + * needs to be allocated. (use lallocx for lines likely to grow.) + * Return a pointer to the new block, or NULL if there isn't + * any memory left. Print a message in the message line if no space. + */ +LINE * +lalloc(used) register int used; { + register LINE *lp; + register int size; + + /* any padding at the end of the structure is used */ + if((size = used + OFFSET(LINE, l_text[0])) < sizeof(LINE)) + size = sizeof(LINE); +#ifdef MALLOCROUND + MALLOCROUND(size); /* round up to a size optimal to malloc */ +#endif + if((lp = (LINE *)malloc((unsigned)size)) == NULL) { + ewprintf("Can't get %d bytes", size); + return (LINE *)NULL; + } + lp->l_size = size - OFFSET(LINE, l_text[0]); + lp->l_used = used; + return lp; +} + +/* + * Like lalloc, only round amount desired up because this line will + * probably grow. We always make room for at least one more char. + * (thus making 0 not a special case anymore.) + */ +LINE * +lallocx(used) +int used; +{ + register int size; + register LINE *lp; + + size = (NBLOCK+used) & ~(NBLOCK-1); + if((lp = lalloc(size)) != NULL) lp->l_used = used; + return lp; +} + +/* + * Delete line "lp". Fix all of the + * links that might point at it (they are + * moved to offset 0 of the next line. + * Unlink the line from whatever buffer it + * might be in. Release the memory. The + * buffers are updated too; the magic conditions + * described in the above comments don't hold + * here. + */ +VOID +lfree(lp) register LINE *lp; { + register BUFFER *bp; + register WINDOW *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; + free((char *) lp); +} + +/* + * This routine gets called when + * a character is changed 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(flag) register int flag; { + register WINDOW *wp; + + if ((curbp->b_flag&BFCHG) == 0) { /* First change, so */ + flag |= WFMODE; /* update mode lines. */ + curbp->b_flag |= BFCHG; + } + for(wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_bufp == curbp) { + wp->w_flag |= flag; + if(wp != curwp) wp->w_flag |= WFHARD; + } + } +} + +/* + * 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. + */ +linsert(n, c) +int n; +{ + register char *cp1; + register char *cp2; + register LINE *lp1; + LINE *lp2; + LINE *lp3; + register int doto; + register RSIZE i; + WINDOW *wp; + + lchange(WFEDIT); + lp1 = curwp->w_dotp; /* Current line */ + if (lp1 == curbp->b_linep) { /* At the end: special */ + /* (now should only happen in empty buffer */ + if (curwp->w_doto != 0) { + ewprintf("bug: linsert"); + return FALSE; + } + if ((lp2=lallocx(n)) == NULL) /* Allocate new line */ + return FALSE; + lp3 = lp1->l_bp; /* Previous line */ + lp3->l_fp = lp2; /* Link in */ + 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; + } + /*NOSTRICT*/ + curwp->w_doto = n; + return TRUE; + } + doto = curwp->w_doto; /* Save for later. */ + /*NOSTRICT (2) */ + if (lp1->l_used+n > lp1->l_size) { /* Hard: reallocate */ + if ((lp2=lallocx(lp1->l_used+n)) == NULL) + return FALSE; + cp1 = &lp1->l_text[0]; + cp2 = &lp2->l_text[0]; + while (cp1 != &lp1->l_text[doto]) + *cp2++ = *cp1++; + /*NOSTRICT*/ + cp2 += n; + while (cp1 != &lp1->l_text[lp1->l_used]) + *cp2++ = *cp1++; + lp1->l_bp->l_fp = lp2; + lp2->l_fp = lp1->l_fp; + lp1->l_fp->l_bp = lp2; + lp2->l_bp = lp1->l_bp; + free((char *) lp1); + } else { /* Easy: in place */ + lp2 = lp1; /* Pretend new line */ + /*NOSTRICT*/ + lp2->l_used += n; + cp2 = &lp1->l_text[lp1->l_used]; + + cp1 = cp2-n; + while (cp1 != &lp1->l_text[doto]) + *--cp2 = *--cp1; + } + for (i=0; i<n; ++i) /* Add the characters */ + lp2->l_text[doto+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==curwp || wp->w_doto>doto) + /*NOSTRICT*/ + wp->w_doto += n; + } + if (wp->w_markp == lp1) { + wp->w_markp = lp2; + if (wp->w_marko > doto) + /*NOSTRICT*/ + wp->w_marko += n; + } + } + return TRUE; +} + +/* + * Insert a newline into the buffer + * at the current location of dot in the current + * window. The funny ass-backwards way is no longer used. + */ +lnewline() +{ + register LINE *lp1; + register LINE *lp2; + register int doto; + register int nlen; + WINDOW *wp; + + lchange(WFHARD); + lp1 = curwp->w_dotp; /* Get the address and */ + doto = curwp->w_doto; /* offset of "." */ + if(doto == 0) { /* avoid unnessisary copying */ + if((lp2 = lallocx(0)) == NULL) /* new first part */ + 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; + return TRUE; + } + nlen = llength(lp1) - doto; /* length of new part */ + if((lp2=lallocx(nlen)) == NULL) /* New second half line */ + 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; + for(wp = wheadp; wp != NULL; wp = wp->w_wndp) { /* Windows */ + 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; + } + } + return TRUE; +} + +/* + * This function deletes "n" bytes, + * starting at dot. It understands how do 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. + */ +ldelete(n, kflag) RSIZE n; { + register char *cp1; + register char *cp2; + register LINE *dotp; + register int doto; + register RSIZE chunk; + WINDOW *wp; + + /* + * HACK - doesn't matter, and fixes back-over-nl bug for empty + * kill buffers. + */ + if (kused == kstart) kflag = KFORW; + + while (n != 0) { + dotp = curwp->w_dotp; + doto = curwp->w_doto; + if (dotp == curbp->b_linep) /* Hit end of buffer. */ + return FALSE; + chunk = dotp->l_used-doto; /* Size of chunk. */ + if (chunk > n) + chunk = n; + if (chunk == 0) { /* End of line, merge. */ + if(dotp == lback(curbp->b_linep)) + return FALSE; /* End of buffer. */ + lchange(WFHARD); + if (ldelnewline() == FALSE + || (kflag!=KNONE && kinsert('\n', kflag)==FALSE)) + return FALSE; + --n; + continue; + } + lchange(WFEDIT); + cp1 = &dotp->l_text[doto]; /* Scrunch text. */ + cp2 = cp1 + chunk; + if (kflag == KFORW) { + while (ksize - kused < chunk) + if (kgrow(FALSE) == FALSE) return FALSE; + bcopy(cp1, &(kbufp[kused]), (int) chunk); + kused += chunk; + } else if (kflag == KBACK) { + while (kstart < chunk) + if (kgrow(TRUE) == FALSE) return FALSE; + bcopy(cp1, &(kbufp[kstart-chunk]), (int) chunk); + kstart -= chunk; + } else if (kflag != KNONE) panic("broken ldelete call"); + while (cp2 != &dotp->l_text[dotp->l_used]) + *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; + } + return TRUE; +} + +/* + * Delete a newline. 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, and this makes + * the kill buffer work "right". 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. + */ +ldelnewline() { + register LINE *lp1; + register LINE *lp2; + register WINDOW *wp; + LINE *lp3; + + lp1 = curwp->w_dotp; + lp2 = lp1->l_fp; + if (lp2 == curbp->b_linep) /* At the buffer end. */ + return TRUE; + 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((char *) 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((char *) lp1); + free((char *) 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). + */ +lreplace(plen, st, f) +register RSIZE plen; /* length to remove */ +char *st; /* replacement string */ +int f; /* case hack disable */ +{ + register RSIZE rlen; /* replacement length */ + register int rtype; /* capitalization */ + register int c; /* used for random characters */ + register int doto; /* offset into line */ + + /* + * Find the capitalization of the word that was found. + * f says use exact case of replacement string (same thing that + * happens with lowercase found), so bypass check. + */ + /*NOSTRICT*/ + (VOID) backchar(FFARG | FFRAND, (int) plen); + rtype = _L; + c = lgetc(curwp->w_dotp, curwp->w_doto); + if (ISUPPER(c)!=FALSE && f==FALSE) { + rtype = _U|_L; + if (curwp->w_doto+1 < llength(curwp->w_dotp)) { + c = lgetc(curwp->w_dotp, curwp->w_doto+1); + if (ISUPPER(c) != FALSE) { + rtype = _U; + } + } + } + + /* + * make the string lengths match (either pad the line + * so that it will fit, or scrunch out the excess). + * be careful with dot's offset. + */ + rlen = strlen(st); + doto = curwp->w_doto; + if (plen > rlen) + (VOID) ldelete((RSIZE) (plen-rlen), KNONE); + else if (plen < rlen) { + if (linsert((int)(rlen-plen), ' ') == FALSE) + return FALSE; + } + curwp->w_doto = doto; + + /* + * do the replacement: If was capital, then place first + * char as if upper, and subsequent chars as if lower. + * If inserting upper, check replacement for case. + */ + while ((c = CHARMASK(*st++)) != '\0') { + if ((rtype&_U)!=0 && ISLOWER(c)!=0) + c = TOUPPER(c); + if (rtype == (_U|_L)) + rtype = _L; + if (c == CCHR('J')) { + if (curwp->w_doto == llength(curwp->w_dotp)) + (VOID) forwchar(FFRAND, 1); + else { + if (ldelete((RSIZE) 1, KNONE) != FALSE) + (VOID) lnewline(); + } + } else if (curwp->w_dotp == curbp->b_linep) { + (VOID) linsert(1, c); + } else if (curwp->w_doto == llength(curwp->w_dotp)) { + if (ldelete((RSIZE) 1, KNONE) != FALSE) + (VOID) linsert(1, c); + } else + lputc(curwp->w_dotp, curwp->w_doto++, c); + } + lchange(WFHARD); + return (TRUE); +} + +/* + * Delete all of the text + * saved in the kill buffer. Called by commands + * when a new kill context is being created. The kill + * buffer array is released, just in case the buffer has + * grown to immense size. No errors. + */ +VOID +kdelete() { + if (kbufp != NULL) { + free((char *) 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. + */ +kinsert(c, dir) { + + if (kused == ksize && dir == KFORW && kgrow(FALSE) == FALSE) + return FALSE; + if (kstart == 0 && dir == KBACK && kgrow(TRUE) == 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. back is true if + * we are trying to get space at the beginning of the kill buffer. + */ +kgrow(back) { + register int nstart; + register 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 = (back == TRUE) ? (kstart + KBLOCK) : (KBLOCK / 4) ; + bcopy(&(kbufp[kstart]), &(nbufp[nstart]), (int) (kused-kstart)); + if (kbufp != NULL) + free((char *) 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. + */ +kremove(n) { + if (n < 0 || n + kstart >= kused) + return -1; + return CHARMASK(kbufp[n + kstart]); +} diff --git a/usr.bin/mg/macro.c b/usr.bin/mg/macro.c new file mode 100644 index 00000000000..94d14e28dc8 --- /dev/null +++ b/usr.bin/mg/macro.c @@ -0,0 +1,83 @@ +/* keyboard macros for MicroGnuEmacs 1x */ + +#ifndef NO_MACRO +#include "def.h" +#include "key.h" +#define EXTERN +#define INIT(i) = (i) +#include "macro.h" + +/*ARGSUSED*/ +definemacro(f, n) +int f, n; +{ + register LINE *lp1; + LINE *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((char *)lp1); + } + free((char *)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*/ +finishmacro(f, n) +int f, n; +{ + macrodef = FALSE; + ewprintf("End Keyboard Macro Definition"); + return TRUE; +} + +/*ARGSUSED*/ +executemacro(f, n) +int f, n; +{ + int i, j; + PF funct; + int universal_argument(); + int flag, num; + + if(macrodef || + (macrocount >= MAXMACRO && macro[MAXMACRO].m_funct != finishmacro)) + 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; +} +#endif diff --git a/usr.bin/mg/macro.h b/usr.bin/mg/macro.h new file mode 100644 index 00000000000..9ce7f892f67 --- /dev/null +++ b/usr.bin/mg/macro.h @@ -0,0 +1,23 @@ +/* definitions for keyboard macros */ + +#ifndef EXTERN +#define EXTERN extern +#define INIT(i) +#endif + +#define MAXMACRO 256 /* maximum functs in a macro */ + +EXTERN int inmacro INIT(FALSE); +EXTERN int macrodef INIT(FALSE); +EXTERN int macrocount INIT(0); + +EXTERN union { + PF m_funct; + int m_count; /* for count-prefix */ +} macro[MAXMACRO]; + +EXTERN LINE *maclhead INIT(NULL); +EXTERN LINE *maclcur; + +#undef EXTERN +#undef INIT diff --git a/usr.bin/mg/main.c b/usr.bin/mg/main.c new file mode 100644 index 00000000000..6b5625af0b7 --- /dev/null +++ b/usr.bin/mg/main.c @@ -0,0 +1,143 @@ +/* + * Mainline + */ +#include "def.h" +#ifndef NO_MACRO +#include "macro.h" +#endif + +int thisflag; /* Flags, this command */ +int lastflag; /* Flags, last command */ +int curgoal; /* Goal column */ +BUFFER *curbp; /* Current buffer */ +WINDOW *curwp; /* Current window */ +BUFFER *bheadp; /* BUFFER listhead */ +WINDOW *wheadp = (WINDOW *)NULL; /* WINDOW listhead */ +char pat[NPAT]; /* Pattern */ +#ifndef NO_DPROMPT +extern char prompt[], *promptp; /* delayed prompting */ +#endif + +static VOID edinit(); + +VOID +main(argc, argv) +int argc; +char **argv; +{ +#ifndef NO_STARTUP + char *startupfile(); +#endif + char *cp; + VOID vtinit(), makename(), eerase(); + BUFFER *findbuffer(); + +#ifdef SYSINIT + SYSINIT; /* system dependent. */ +#endif + vtinit(); /* Virtual terminal. */ +#ifndef NO_DIR + dirinit(); /* Get current directory */ +#endif + edinit(); /* 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(); +#ifndef NO_STARTUP /* User startup file. */ + if ((cp = startupfile((char *)NULL)) != NULL) + (VOID) load(cp); +#endif + while (--argc > 0) { + cp = adjustname(*++argv); + curbp = findbuffer(cp); + (VOID) showbuffer(curbp, curwp, 0); + (VOID) readin(cp); + } + thisflag = 0; /* Fake last flags. */ + for(;;) { +#ifndef NO_DPROMPT + *(promptp = prompt) = '\0'; + if(epresf == KPROMPT) eerase(); +#endif + update(); + lastflag = thisflag; + thisflag = 0; + switch(doin()) { + case TRUE: break; + case ABORT: + ewprintf("Quit"); /* and fall through */ + case FALSE: + default: + ttbeep(); +#ifndef NO_MACRO + macrodef = FALSE; +#endif + } + } +} + +/* + * Initialize default buffer and window. + */ +static VOID +edinit() { + register BUFFER *bp; + register WINDOW *wp; + + bheadp = NULL; + bp = bfind("*scratch*", TRUE); /* Text buffer. */ + wp = (WINDOW *)malloc(sizeof(WINDOW)); /* Initial window. */ + if (bp==NULL || wp==NULL) panic("edinit"); + curbp = bp; /* Current ones. */ + wheadp = wp; + curwp = wp; + wp->w_wndp = NULL; /* Initialize window. */ + wp->w_bufp = bp; + bp->b_nwnd = 1; /* Displayed. */ + wp->w_linep = wp->w_dotp = bp->b_linep; + wp->w_doto = 0; + wp->w_markp = NULL; + wp->w_marko = 0; + wp->w_toprow = 0; + wp->w_ntrows = nrow-2; /* 2 = mode, echo. */ + wp->w_force = 0; + wp->w_flag = WFMODE|WFHARD; /* 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*/ +quit(f, n) +{ + register int s; + VOID vttidy(); + + if ((s = anycb(FALSE)) == ABORT) return ABORT; + if (s == FALSE + || eyesno("Some modified buffers exist, really exit") == TRUE) { + vttidy(); +#ifdef SYSCLEANUP + SYSCLEANUP; +#endif + 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*/ +ctrlg(f, n) +{ + return ABORT; +} diff --git a/usr.bin/mg/match.c b/usr.bin/mg/match.c new file mode 100644 index 00000000000..e49268b9b54 --- /dev/null +++ b/usr.bin/mg/match.c @@ -0,0 +1,189 @@ +/* + * Name: MicroEMACS + * 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. + * + * Since I often edit Scribe code, I've made it possible to + * blink arbitrary characters -- just bind delimiter characters + * to "blink-matching-paren-hack" + */ +#include "def.h" +#include "key.h" + +static int balance(); +static VOID displaymatch(); + +/* 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. + * This is to allow delimiters in Scribe documents to be matched. + */ + +static struct balance { + char left, right; +} bal[] = { + { '(', ')' }, + { '[', ']' }, + { '{', '}' }, + { '<', '>' }, + { '\0','\0'} +}; + +/* + * Self-insert character, then show matching character, + * if any. Bound to "blink-matching-paren-command". + */ + +showmatch(f, n) +{ + register int i, s; + + if (f & FFRAND) return FALSE; + for (i = 0; i < n; i++) { + if ((s = selfinsert(FFRAND, 1)) != TRUE) + return s; + if (balance() != TRUE) /* unbalanced -- warn user */ + 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 balance() +{ + register LINE *clp; + register int cbo; + int c; + int i; + int rbal, lbal; + int depth; + + 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; + + depth = 0; /* init nesting depth */ + + for (;;) { + if (cbo == 0) { /* beginning of line */ + clp = lback(clp); + if (clp == curbp->b_linep) + return (FALSE); + cbo = llength(clp)+1; + } + if (--cbo == llength(clp)) /* end of line */ + c = '\n'; + 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(clp, cbo) +register LINE *clp; +register int cbo; +{ + register LINE *tlp; + register int tbo; + register int cp; + register int bufo; + register 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_flag |= WFMOVE; + + update(); /* show match */ + sleep(1); /* wait a bit */ + + curwp->w_dotp = tlp; /* return to old position */ + curwp->w_doto = tbo; + curwp->w_flag |= WFMOVE; + update(); + } + else { /* match not in this window so display line in echo area */ + bufo = 0; + for (cp = 0; cp < llength(clp); cp++) { /* expand tabs */ + 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/usr.bin/mg/mg.1 b/usr.bin/mg/mg.1 new file mode 100644 index 00000000000..a44951f19e7 --- /dev/null +++ b/usr.bin/mg/mg.1 @@ -0,0 +1,139 @@ +.TH MG 1 +.SH NAME +mg \- Micro Gnu emacs +.SH SYNOPSIS +.B mg +[ +.I files +] +.SH DESCRIPTION +.B Mg +is intended as a micro version of Gnu Emacs. It is intended primarily +for use on PC's of various kinds, where it may not be practical to +run Gnu Emacs because of its size. However it is also useful on +larger systems for some purposes. Because it is about 1/10 the size +of Gnu Emacs, it starts much faster, and is much less likely to +cause paging. +.LP +Normal editing commands should be identical to Gnu Emacs. It differs +primarily in not having special modes for tasks other than straight +editing, e.g. mail and news, and in not having special modes that +support various programming languages. It does have text justification +and auto-fill mode. It is written directly in C, so there is no +language in which you can write extensions. However you can rebind +keys and change some parameters. There are no limits to line length +or format. Command, buffer, and file name completion and listing can +be done using space and ? respectively. +.LP +.B Mg +is close enough to Gnu Emacs that you can learn it the same way: +using the program +.BR teach-emacs . +.B Teach-emacs +will invoke Gnu Emacs, however the features that it teaches should +work identically on +.BR mg . +.LP +The one major difference is in configuration files. Gnu Emacs uses +a configuration file +.IR .emacs , +which is written in Lisp. +.B Mg +uses its own configuration files, which contain extend mode Emacs +commands (i.e. commands that you could type after doing m-x). +There are two configuration files, +.IR .mg , +and +.IR .mg-TERM . +TERM here represents the name of you terminal type. E.g. if +your terminal type is set to vt100, +.B mg +will use +.I .mg-vt100 +as a startup file. The terminal type startup file is used +first. If either of these files does not exist, +.B mg +will look for a file by the same name (but without the leading +dot) in +.IR /usr/local/lib/mg . +.LP +See the manual for a full list of the commands that can +go in the files. The most commonly +used ones are probably key binding. The following example is +part of a configuration file used to set make +.B mg +respond to the keypad on a Microport SV/AT system. The normal +keys send a sequence of the form <ESC> [ <letter>. I also +want to use keys prefixed by <ESC> as having different meaning. +In order to deal with multi-character sequences, the initial +subsequences must be defined as prefixes. To allow for this, +three prefixes are left undefined in the initial setup. They +are called "extra prefix 1", etc. +.br + ;allow normal pad +.br + global-set-key ^[[ "extra prefix 1" +.br + ;allow prefixed pad +.br + global-set-key ^[^[ "extra prefix 2" +.br + global-set-key ^[^[[ "extra prefix 3" +.br + ;keypad +.br + global-set-key ^[[A previous-line +.br + global-set-key ^[[H scroll-down +.br + ...etc +.br + ;escaped keypad +.br + global-set-key ^[^[[A exchange-point-and-mark +.br + global-set-key ^[^[[H beginning-of-buffer +.br + ...etc +.LP +Here's another example sequence that you may find useful. By default () +and [] are recognized as brackets, so bracket matching can be done. +The following defines {} as brackets, and turns on the mode that causes +the cursor to "blink" to show you matching brackets. +.br + global-set-key } blink-matching-paren-hack +.br + blink-matching-paren +.br + set-default-mode blink +.SH ARGUMENTS +.B Mg +does not take any options. The only arguments you can pass it are +file names. It will do a find-file on each one, reading it into +a buffer. It will leave the last buffer on the screen. If you call +.B mg +from +.BR vnews , +both the original article and your reply will be in separate buffers. +The original article will be showing. Use "c-x b" to switch to the +buffer for your reply. +.SH "SEE ALSO" +.BR gnuemacs (1), +.BR teach-emacs (1) +.SH BUGS +When you type ? to list possible file names, buffer names, etc., +a help buffer is created for the possibilities. In Gnu Emacs, +this buffer goes away the next time you type a real command. +In +.BR mg , +you must use "m-x 1" to get rid of it. +.SH FILES +.LP +.mg - normal startup file +.LP +.mg-TERM - terminal-specific startup file +.LP +/usr/local/lib/mg - directory for system-wide startup files. Files in +this directory do not have the leading dot. +.LP +/usr/doc/mg.doc - full manual diff --git a/usr.bin/mg/mg.doc b/usr.bin/mg/mg.doc new file mode 100644 index 00000000000..fe238aee14d --- /dev/null +++ b/usr.bin/mg/mg.doc @@ -0,0 +1,2202 @@ + + + + + + + + + + + + + + + + + + + The MG Reference Manual + + Release MG2A + + + Sandra J. Loosemore + + + + + + + + + + Copyright (C)1987, Sandra J. Loosemore + This document, or sections of this document, may be freely + + redistributed provided that the copyright notice and the following + disclaimer remain intact: The author bears no responsibilities for + errors in this document or the software it describes; and shall + not be held liable for any indirect, incidental, or consequential + damages. + + + + + + + + + + + + + + +Contents + + + + + 1 Introduction 2 + 1.1 Implementations of MG. . . . . . . . . . . . . . . . . . . . . 3 + 1.2 A Note on Character Sets . . . . . . . . . . . . . . . . . . . 3 + 1.3 Notation and Conventions . . . . . . . . . . . . . . . . . . . 4 + 1.4 Getting Started. . . . . . . . . . . . . . . . . . . . . . . . 5 + 2 Using Commands 7 + 2.1 Command Arguments. . . . . . . . . . . . . . . . . . . . . . . 7 + 2.2 Prefix Arguments . . . . . . . . . . . . . . . . . . . . . . . 7 + 2.3 Aborting . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 + 2.4 Extended Commands. . . . . . . . . . . . . . . . . . . . . . . 8 + 3 Moving the Cursor 9 + + 4 Text Insertion Commands 12 + 5 Killing, Deleting, and Moving Text 14 + + 6 Searching and Replacing 16 + 6.1 Searching. . . . . . . . . . . . . . . . . . . . . . . . . . . 16 + 6.2 Replacing. . . . . . . . . . . . . . . . . . . . . . . . . . . 17 + 6.3 Regular Expressions. . . . . . . . . . . . . . . . . . . . . . 18 + 7 Windows 21 + + 8 Files and Buffers 23 + 8.1 Buffer Manipulation. . . . . . . . . . . . . . . . . . . . . . 23 + 8.2 Reading and Writing Files. . . . . . . . . . . . . . . . . . . 24 + 8.3 Backup Files . . . . . . . . . . . . . . . . . . . . . . . . . 25 + 8.4 Changing the Directory . . . . . . . . . . . . . . . . . . . . 25 + 9 Modes 26 + 9.1 No Tab Mode. . . . . . . . . . . . . . . . . . . . . . . . . . 26 + 9.2 Overwrite Mode . . . . . . . . . . . . . . . . . . . . . . . . 27 + 9.3 Auto Fill. . . . . . . . . . . . . . . . . . . . . . . . . . . 27 + 9.4 Auto Indent. . . . . . . . . . . . . . . . . . . . . . . . . . 27 + 9.5 Blink. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 + + + 1 + + + + + + + + 9.6 Dired Mode . . . . . . . . . . . . . . . . . . . . . . . . . . 28 + + 10 Miscellaneous 30 + 10.1 Help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 + 10.2 Keyboard Macros. . . . . . . . . . . . . . . . . . . . . . . . 30 + 10.3 Changing Case. . . . . . . . . . . . . . . . . . . . . . . . . 31 + 10.4 Odds and Ends. . . . . . . . . . . . . . . . . . . . . . . . . 31 + 11 Customization 33 + 11.1 Key Bindings . . . . . . . . . . . . . . . . . . . . . . . . . 33 + 11.2 Startup Files. . . . . . . . . . . . . . . . . . . . . . . . . 34 +Fundamental Mode Key Bindings 36 + +Index 38 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + +Chapter 1 + + + + +Introduction + + + +MG is a small, fast, and portable Emacs-style text editor intended to be +used by people who can't run a real Emacs for one reason or another --- as +their main editor on smaller machines with limited memory or file space, or +as a ``quick-start'' editor on larger systems, useful for composing short +mail messages and the like. + We've made MG compatible with GNU Emacs because that is the ``big'', +full-featured editor that many of us use regularly and are most familiar +with. GNU Emacs is the creation of Richard M. Stallman, who was also the +author of the original Emacs editor. However, MG is not associated in any +way with the GNU project, and the MG authors individually may or may not +agree with the opinions expressed by Richard Stallman and the GNU project. + MG is largely public domain. You can use, modify, and redistribute MG +as you like. A few modules, however, are copyrighted; specifically, the +regular expression code, the VMS termcap routines, and the Amiga support +code. Look at the source code for the exact copyright restrictions. + There are several other editors in existence which call themselves +MicroEmacs. The original public domain version was written by Dave Conroy +and circulated as version 1.6. Derived from this, there is another PD +version by Dave Conroy numbered v30; a significantly larger PD version by +Daniel Lawrence which is now up to version 3.9; at least one proprietary +implementation; an implementation for the Atari ST with an integrated +command shell, by Prabhaker Mateti; and probably others that we don't know +about. + MG is derived from the v30 MicroEmacs, with key bindings, command names, +and general functionality made more compatible with GNU Emacs. Like v30, +MG is fairly small and quite robust. We have generally resisted the +temptation to overfeaturize. Some features which are large and complex are +flagged for conditional compilation. + Many people have contributed their time to developing, improving, and + + + 3 + + + + + + + +porting MG. Mike Meyer, Mic Kaczmarczik, and Bob Larson deserve particular +mention for their efforts. + Questions, suggestions, and offers of help should be addressed to: + + mg-developers@ucbvax.berkeley.edu (ARPA) + ucbvax!mg-developers (UUCP) + + +1.1 Implementations of MG + +MG runs on many different kinds of hardware under many different operating +systems. Currently, these include: + + - 4.2 and 4.3 BSD Unix (including Ultrix-32) + + - System V Unix + + - VAX/VMS + + - Primos + + - OS9/68k + + - Amiga + + - Atari ST + + - MS-DOS + + + This document describes release MG2A. When we talk of different versions +of MG in this manual, the term version is used to refer to the different +support MG provides for the various machines and operating systems it runs +under, not to different releases of MG itself. For example, we might speak +of how the VMS version of MG differs from the Unix version. + As mentioned above, some MG commands may not be implemented in all +versions; these are noted in the documentation. Some versions of MG also +support features (such as mouse handling) that are not described here. + +1.2 A Note on Character Sets + +MG uses the 128-character ASCII character set, and provides support +for 8-bit characters. Whether the particular version of MG that you +are running knows about extended character sets depends on whether your + + + 4 + + + + + + + +terminal and the host operating system know about them. Moreover, since +there is no standard 8-bit character set, the same character codes will +probably give different glyphs on different systems. Most versions of MG +use the DEC multinational character set. + + +1.3 Notation and Conventions + +In this manual, commands and other things that must be typed in literally +are indicated in a typewriter font, like next-line. Placeholders such as +command argument names use an italic font. + The terms command and function are synonymous. We often speak of a +command being bound to a particular key, although you may actually have to +type more than one character to form a single key. Most commands are bound +to keys with control and meta modifiers. + To type a control character, use the control key on your keyboard like a +shift key: hold down the control key while typing the character. In this +manual, we will indicate control characters like C-x --- here, typing the +character ``x'' while holding down the control key. + Some keyboards also have a meta key that works like the control key. +(It may be labelled something else; on the Atari ST, for example, the key +marked ``Alternate'' is the meta key.) If your keyboard doesn't have a +meta key, don't panic. You can also use the escape key as a meta prefix; +first type the escape, and then the character. Meta characters will be +indicated as M-x. + Besides the meta prefix, two other characters are used as prefixes: C-x +and C-h. A few keys have special notation: SPC is the space character, +DEL is the delete or rubout character, RET is carriage return, and ESC +is the escape character. NUL is the null character (ASCII 0), which is +usually equivalent to either C-SPC or C-@. + Uppercase and lowercase characters are generally equivalent in command +keystrokes. + When you run MG from a shell, command line arguments are interpreted as +the names of files you want to visit, or edit. Each file is read into a +buffer in memory. No changes are actually made to the file until you ask +it to be written out to disk. + Within MG, the large top part of the screen serves as a window into +the buffer being edited. Below this is the mode line, which displays the +name of the buffer. Finally, at the very bottom of the screen, there is +a one-line minibuffer which is used for displaying messages and answering +questions. + MG keeps track of two pointers into each window, the point and the mark. +The cursor appears at the point in the current window, and we often speak +of moving the cursor rather than of moving the point. The text between the + + + 5 + + + + + + + +point and the mark is referred to as the region. + Some commands deal with words and paragraphs. Generally, whitespace and +punctuation separate words. Lines that are empty or that contain only +spaces or tabs separate paragraphs without being part of a paragraph. A +non-empty line that starts with a space or tab also begins a new paragraph. + A number of commands are defined as toggles. If no prefix argument is +supplied, these commands toggle an action. The action is turned on if a +negative or zero argument is supplied, and turned on if a positive argument +is supplied. + + +1.4 Getting Started + +This document is intended primarily as a reference manual. If you have +never used any Emacs-like text editor before, it is strongly suggested that +you run the on-line tutorial supplied with the MG distribution, instead of +reading this manual. + Do not be put off by the large number of commands described in this +manual! It is possible to get by with only a handful of basic commands. +Here are the ones that are probably used most frequently: + +C-p Move the cursor to the previous line + +C-n Move the cursor to the next line + +C-b Move the cursor backwards + +C-f Move the cursor forwards + +C-v Scroll forwards one screenful + +M-v Scroll backwards one screenful + +M-< Go to the beginning of the buffer + +M-> Go to the end of the buffer + +C-a Go to the beginning of the line + +C-e Go to the end of the line + +DEL Delete the previous character + +C-k Kill (delete) to the end of line + + + 6 + + + + + + + +C-y Reinsert killed text. + +C-x C-c Exit MG + +C-x C-s Save the current buffer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + + + + + + + +Chapter 2 + + + + +Using Commands + + + +2.1 Command Arguments + +Some commands require arguments. For example, if you want to read a file +into a buffer, you must type in the name of the file. In the descriptions +of commands in this manual, if arguments are required, they are listed +following the command name. + MG prompts for command arguments in the minibuffer. Within the +minibuffer, the following characters can be used for editing: + + +DEL, C-h Erase the last character. + +C-x, C-u Erase the entire input line. + +C-w Erase to the beginning of the previous word. + +C-q, n Quote the next character typed. + +RET Signifies that you have completed typing in the argument. + +C-g Abort the command in progress. + +2.2 Prefix Arguments + +All commands accept an optional numeric prefix argument. This is often +interpreted as a repetition count. For example, the function next-line, +if given a prefix argument, will move the cursor forward that many lines; +without an argument, it will move the cursor forward one line. A few +commands behave differently if given a prefix argument than they do without + + + 8 + + + + + + + +one, and others ignore the prefix argument entirely. + +digit-argument M-0, M-1, M-2, M-3, M-4, M-5, M-6, M-7, M-8, M-9 +negative-argument M-- +One way to specify a command argument is to use the escape key as a meta +prefix, and then type one or more digits. A dash may be used for a +negative argument. + +universal-argument C-u + +Another way to specify a command prefix is to type C-u. Typing one C-u is +equivalent to a prefix argument of 4, typing two gives a value of 16, and +so on. In addition, you can type digits following C-u to form a numeric +prefix argument. + +2.3 Aborting + + +keyboard-quit C-g +Typing C-g cancels any command. It is particularly useful for cancelling a +command when MG is prompting for input in the minibuffer. + + +2.4 Extended Commands + + +execute-extended-command command M-x +Commands that are not bound to keys can be executed through execute +extended-command. If a prefix argument is supplied, it is passed to the +command being executed. + + + + + + + + + + + + + + + + + 9 + + + + + + + + + + + + + +Chapter 3 + + + + +Moving the Cursor + + + +The commands described in this chapter move the cursor (sometimes called +the point or dot) within the current window. Commands which set the mark +are included here as well. + +backward-char C-b +Moves the cursor backward (left) one character. If the cursor is at the +left margin, it will be moved to the end of the previous line. + +backward-paragraph M-[ +Moves the cursor backwards to the beginning of the current paragraph, or +to the beginning of the previous paragraph if the cursor is already at the +beginning of a paragraph. + +backward-word M-b + +Moves the cursor backwards to the beginning of the current word, or to the +beginning of the previous word if the cursor is already at the beginning of +a word. + +beginning-of-buffer M-< +Moves the cursor backwards to the beginning of the buffer. + +beginning-of-line C-a +Moves the cursor backwards to the beginning of the current line. This +command has no effect if the cursor is already at the beginning of the +line. + +end-of-buffer M-> +Moves the cursor forwards to the end of the buffer. + + + 10 + + + + + + + + +end-of-line C-e +Moves the cursor forwards to the end of the current line. This command has +no effect if the cursor is already at the end of the line. + +exchange-point-and-mark C-x C-x + +Set the mark at the current cursor position, and move the cursor to the old +location of the mark. + +forward-char C-f +Moves the cursor forwards one character. If the cursor is at the end of a +line, it will be moved to the first character on the next line. + +forward-paragraph M-] +Moves the cursor forwards to the next paragraph delimiter. + +forward-word M-f +Moves the cursor forwards to the end of the current word, or to the end of +the next word if the cursor is already at the end of a word. + +goto-line line-number +Moves the cursor to the beginning of line line-number in the buffer. + +next-line C-n + +Moves the cursor down one line. The cursor remains in the same column +unless it would be past the end of the line, in which case it is moved to +the end of the line. At the end of the buffer, C-n will create new lines. + +previous-line C-p +Moves the cursor up one line. The cursor remains in the same column unless +it would be past the end of the line, in which case it is moved to the end +of the line. + +recenter C-l +Redraws the entire screen, scrolling the current window if necessary so +that the cursor is near the center. With a positive prefix argument n, the +window is scrolled so that the cursor is n lines from the top. A negative +prefix argument puts the cursor that many lines from the bottom of the +window. + +redraw-display + + + + 11 + + + + + + + +Redraws the entire screen, but never scrolls. + +scroll-down M-v +Scrolls the display down (moving backward through the buffer). Without an +argument, it scrolls slightly less than one windowful. A prefix argument +scrolls that many lines. + +scroll-one-line-down +scroll-one-line-up + +These functions are similar to scroll-down and scroll-up (respectively), +but when invoked without an argument, cause the display to scroll by one +line only. These functions are enabled by defining the compile-time option +GOSMACS. + +scroll-other-window M-C-v +Scrolls the ``other'' window forward as for scroll-up. + +scroll-up C-v +Scrolls the display up (moving forward through the buffer). Without an an +argument, it scrolls slightly less than one windowful. A prefix argument +scrolls that many lines. + +set-mark-command NUL +Set the mark at the current cursor position. + +what-cursor-position C-x = +Prints some information in the minibuffer about where the cursor is. + + + + + + + + + + + + + + + + + + + 12 + + + + + + + + + + + + + +Chapter 4 + + + + +Text Insertion Commands + + + +The usual way to insert text into a buffer is simply to type the +characters. The default binding for all of the printing characters +(self-insert-command) causes them to be inserted literally at the cursor +position. + +insert string +Insert string into the current buffer at the cursor position. + +newline RET +Insert a line break into the current buffer at the cursor position, moving +the cursor forward to the beginning of the new line. + +newline-and-indent C-j + +Insert a line break into the current buffer at the cursor position, then +add extra whitespace so that the cursor is aligned in the same column as +the first non-whitespace character in the previous line. + +open-line C-o +Inserts a line break into the current buffer at the current position, but +does not move the cursor forward. + +quoted-insert C-q +This command acts as a prefix to cancel the normal interpretation of the +next keystroke. If C-q is followed by one to three octal digits, it is +interpreted as the code of the character to insert. Otherwise a single +key is read and the character typed is inserted into the buffer instead +of interpreted as a command. This is used for inserting literal control +characters into a buffer. + + + 13 + + + + + + + + +self-insert-command +This is the default binding for keys representing printable characters. +The character is inserted into the buffer at the cursor position, and the +cursor moved forward. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 14 + + + + + + + + + + + + + +Chapter 5 + + + + +Killing, Deleting, and Moving Text + + + +When text is deleted, it is erased completely. Killing text, on the other +hand, moves it into a temporary storage area called the kill buffer. The +saved text in the kill buffer is erased when another block of text is +killed. Until then, however, you can retrieve text from the kill buffer. +This can be used to move or copy blocks of text, as well as to restore +accidentally killed text. + +backward-kill-word M-DEL +Kill the text backwards from the cursor position to the beginning of the +current word. Typing M-DEL several times in succession prepends each +killed word to the kill buffer. + +copy-region-as-kill M-w +Copies the text in the region into the kill buffer, without removing it +from the current buffer. + +delete-backward-char DEL + +Deletes the character to the left of the cursor. + +delete-blank-lines C-x C-o +Deletes all blank lines after the current line, and if the current line is +blank, deletes it and all blank lines preceeding it as well. + +delete-char C-d +Deletes the character underneath the cursor. + +delete-horizontal-space M-n +Deletes all spaces and tabs on either side of the cursor. + + + 15 + + + + + + + + +just-one-space M-SPC +This is like delete-horizontal-space, except it leaves a single space at +the cursor position. + +kill-line C-k + +If no prefix argument is specified, this function kills text up to the next +newline; or if the cursor is at the end of a line, the newline is killed. +A prefix argument specifies how many lines to kill. Typing C-k several +times in succession appends each line to the kill buffer. + +kill-paragraph +This command kills the entire paragraph containing the cursor. If the +cursor is positioned between paragraphs, the next paragraph is killed. + +kill-region C-w +The region (all text between point and mark) is killed. + +kill-word M-d +Text is killed forward from the cursor position to the next end of word. +If the cursor is at the end of the word, then the next word is killed. +Typing M-d several times appends the killed text to the kill buffer. + +yank C-y +Text is copied from the kill buffer into the current buffer at the cursor +position. The cursor is moved to the end of the inserted text. + + + + + + + + + + + + + + + + + + + + 16 + + + + + + + + + + + + + +Chapter 6 + + + + +Searching and Replacing + + + +6.1 Searching + +The ordinary search command in MG differs from that in many other editors +in that it is incremental: it begins searching as soon as you begin typing +the search string, instead of waiting for you to type the entire string. + All of the search commands described in this section are +case-insensitive. + +isearch-backward pattern C-r +isearch-forward pattern C-s + +These commands perform an incremental search backward and forward +(respectively) for pattern. MG will move the cursor to the place in the +buffer that matches as much of the pattern as you have typed so far, as +each character is entered. + Within the incremental search, the following characters are interpreted +specially: + +DEL Erase the last character in the search string. + +ESC Stop searching; exit from incremental search mode, leaving + the cursor where the search brought it. + +C-g If a match has been found, exits from incremental search but + leaves the cursor in its original position. If the search + has failed, this will just erase the characters which have + not been found from the end of the search pattern. In this + case, you must type C-g again to abort the search. + + + + 17 + + + + + + + +C-s Search forward for the next occurrence of the same pattern. + +C-r Search backward for the previous occurrence of the same + pattern. + +C-q ``Quotes'' the next character typed, forcing it to be + interpreted as a literal character in the search pattern. + + In addition, normal commands such as C-a that do not have special +meanings within incremental search cause the search to be terminated, and +then are executed in the ordinary way. + +search-again +search-backward pattern M-r +search-forward pattern M-s + +These commands perform ordinary, non-incremental searches. Search-again +uses the same pattern and direction as the previous search. + +6.2 Replacing + + +query-replace pattern replacement M-% +The primary replace command in MG is an interactive query replace. MG +searches forward for occurrences of pattern, and asks you what to do about +each one. The choices are: + + +SPC Replace this match with replacement, and go on to the next. + +DEL Skip to the next match without replacing this one. + +. Replace this match, and then quit. + +! Replace all remaining occurrences without asking again. + +ESC Quit. + + By default, query-replace adjusts the case of lower-case letters in +the replacement string to match that of the particular occurrence of the +pattern; for example, replacing ``Foo'' with ``bar'' results in ``Bar''. +Upper case letters in the replacement string are always left uppercase. In +addition, supplying a prefix argument will also tell query-replace to leave +the case of the replacement string as-is. + + + 18 + + + + + + + + Note that query-replace always performs a case-insensitive search. + + +6.3 Regular Expressions + +Regular expressions provide a means for specifying complex search patterns, +instead of just a literal string. The commands in this section are +available only if MG is compiled with the REGEX option defined. + Regular expression syntax uses the following rules. Most characters +in a regular expression are considered to be ordinary characters, and +will match themselves and nothing else. The exceptions are the special +characters listed below. + +. Matches any single character except a newline. + +* A suffix operator that matches zero or more repetitions of + the (smallest) preceding regular expression. + ++ A suffix operator that matches one or more repetitions of + the (smallest) preceding regular expression. + +? A suffix operator that matches either zero or one occurence + of the (smallest) preceding regular expression. + +[... ] Matches any one character listed in the character set + between the square brackets. See examples below. + +^ Matches at the beginning of a line. + +$ Matches at the end of a line. + +n Except for the situations listed below, acts as a prefix + operator which causes the character following to be treated + as an ordinary character. + +n| An infix binary or operator. It applies to the two largest + surrounding expressions. + +n(... n) A grouping construct, usually used to specify a larger + expression for postfix operators such as * or to limit the + scope of operands to \|. + +ndigit Matches the same text matched by the digitth \(...\) + construct. These are numbered from 1 to 9 in the order that + + + 19 + + + + + + + + the open-parentheses appear. + +n` Matches at the beginning of the buffer. + +n' Matches at the end of the buffer. + +nb Matches at the beginning or end of a word. + +nB Matches anyplace except at the beginning or end of a word. + +n< Matches at the beginning of a word. + +n> Matches at the end of a word. + +nw Matches any word-constituent character. + +nW Matches any character which is not a word-constituent. + + Some examples may help clarify the rules. + + +foo Matches the literal string foo. + +;.* Matches all strings which begin with a semicolon and + continue to the end of a line. + +c[ad]+r Matches strings of the form car, cdr, caar, cadr, and so on. + +[a-z] Matches any lowercase letter. + +[^a-z] Matches any character except lowercase letters. + +[0-9+---] Matches a digit or sign. + +n(foon|barn) Matches either the string foo or the string bar. + + +count-matches pattern +count-non-matches pattern +These commands count the number of lines which do or do not (respectively) +match the specified pattern. + +delete-matching-lines pattern +delete-non-matching-lines pattern + + + 20 + + + + + + + +These commands delete all lines which do or do not (respectively) match the +specified pattern. + +query-replace-regexp pattern replacement +This is the regular expression version of query-replace. + The replacement string may be a constant, or it can refer to all or +part of the string matched by the pattern. \& in the replacement string +expands into the entire text being replaced, while \n (where n is a number) +replaces the nth parenthesized expression in pattern. + +re-search-again +re-search-backward pattern +re-search-forward pattern + +These are the regular expression equivalents of the ordinary non- +incremental search commands. + +set-case-fold-search +This command toggles an internal variable that controls whether the regular +expression search and replace commands pay attention to case. By default, +regular expression searches are case-insensitive. Ordinary searches are +always case-insensitive and are not affected by the setting of this +variable. + + + + + + + + + + + + + + + + + + + + + + + + 21 + + + + + + + + + + + + + +Chapter 7 + + + + +Windows + + + +MG initially has only one text window displayed. However, you can have as +many windows as will fit on the screen. Each window has its own mode line +and must display at least two lines of text. (Note that a MG's ``windows'' +are distinct from the ``windows'' handled by screen managers such as the X +Window System.) + Multiple windows may be used to display different buffers. You can also +have the same buffer displayed in more than one window, which is useful if +you want to see one part of a file at the same time as you are editing +another part. + Although many windows can be displayed at once, only one window is +active at any given time. This is the window where the cursor appears. + Some commands refer to the ``other'' window. This is the window +directly below the current window, or the top window if you are in the +bottom window. + +delete-other-windows C-x 1 +Makes the current window the only window. + +delete-window C-x 0 +Deletes the current window, making the ``other'' window the current window. +This command doesn't do anything useful if there is only one window being +displayed. + +enlarge-window C-^ + +Makes the current window larger. Without a prefix argument, the window +grows one line; otherwise, the prefix argument specifies how many lines to +grow. + + + + 22 + + + + + + + +other-window C-x o +Makes the ``other'' window the current window. + +previous-window + +This is like other-window, except that it cycles through the windows in +reverse order. This command is available only if MG was compiled with the +GOSMACS option defined. + +shrink-window +Makes the current window smaller. Without a prefix argument, the window +loses one line; otherwise, the prefix argument specifies how many lines go +away. + +split-window-vertically C-x 2 +Split the current window into two windows, both using the same buffer. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 23 + + + + + + + + + + + + + +Chapter 8 + + + + +Files and Buffers + + + +Most buffers are used to contain a file being edited. It is also possible +to have buffers that are not associated with any file; MG uses these for +purposes such as displaying help text, for example. However, since most +commands for dealing with files also deal with buffers, we have grouped all +of these commands together into one chapter. + +8.1 Buffer Manipulation + + +insert-buffer buffer-name + +Inserts the contents of the named buffer into the current buffer at the +cursor location. The cursor moves to the end of the inserted text. + +kill-buffer buffer-name C-x k +The named buffer and its contents are deleted. If the buffer has been +marked as modified, MG will ask you if you really want to delete it. Note +that, contrary to its name, this command does not save the buffer contents +in the kill buffer. + If a buffer is being displayed in a window when it is deleted, MG will +find some other buffer to display in the same window. + +list-buffers C-x C-b +This command writes information about the buffers currently in use to a +buffer named *Buffer List*. This buffer is then displayed in the ``other'' +window; if there is only one window, this command will split the screen +into two windows. + +not-modified M-~ + + + 24 + + + + + + + +This command makes MG think that the current buffer has not been modified, +even if it really has been changed. This affects the behavior of the +kill-buffer and the buffer-saving commands described below. + MG indicates modified buffers with two stars at the left end of the mode +line. + +switch-to-buffer buffer-name C-x b +The current window is mapped onto the named buffer. If there isn't already +a buffer with that name around, MG will create one. + +switch-to-buffer-other-window buffer-name C-x 4 b + +This command works like switch-to-buffer, except that the ``other'' window +is used. If there is only one window, this command splits the screen into +two windows and maps the named buffer onto one of them. + +8.2 Reading and Writing Files + + +find-file file-name C-x f +find-file-other-window file-name C-x 4 C-f +These commands are analagous to switch-to-buffer and switch-to-buffer- +other-window, respectively. The difference is that these commands look for +a buffer associated with the named file. If no matching buffer is found, +MG will create a new buffer with a name derived from the filename, and +attempt to read the file into the buffer. If the named file cannot be +opened, the buffer remains empty. + +insert-file file-name C-x i + +This command reads in the contents of the named file into the current +buffer at the cursor position. The cursor remains in the same place. + +save-buffer C-x C-s +If the current buffer has been modified, it is saved. Buffers that are not +associated with files cannot be written out with this command. + +save-buffers-kill-emacs C-x C-c +This command is used to leave MG and return control to the shell or other +program that was used to start MG. If there are modified buffers, MG will +ask you if you want to save them before exiting. + +save-some-buffers C-x s +MG will ask you if you want to save modified buffers that are associated + + + 25 + + + + + + + +with files. + +write-file file-name C-x C-w +The current buffer is written out using the file name supplied. This +is useful for saving buffers that are not associated with files, or for +writing out a file with a different name than what was used to read it in. + + +8.3 Backup Files + +MG provides a way to save a copy of the original version of files which +have been modified and then written out again. The backup copy reflects +the state of the file as it existed the first time it was read into MG. The +name used for the backup file varies, depending on the operating system. + This feature is disabled if MG is compiled with NOBACKUP defined. + +make-backup-files +This command is a toggle which controls the state of an internal variable +that determines whether MG creates backup files. + + +8.4 Changing the Directory + +The commands in this section are disabled by defining NODIR. + +cd directory-name +This command changes MG's notion of the ``current'' directory or pathname. +This is used to supply defaults for functions that read or write files. + The syntax for directory-name is obviously specific to the particular +operating system MG is running on. + +pwd +Display what MG thinks is the current directory. + + + + + + + + + + + + + + 26 + + + + + + + + + + + + + +Chapter 9 + + + + +Modes + + + +Modes are used to locally alter the bindings of keys on a buffer-by-buffer +basis. MG is normally in fundamental mode, and these are the bindings that +are listed with the command descriptions in this manual. Modes define +additional keymaps that are searched for bindings before the fundamental +mode bindings are examined; see the section on key binding below for more +details on how this works. + +set-default-mode mode-name +Normally, when MG visits a file, it puts the associated buffer into +fundamental mode. Using the set-default-mode command, you can specify that +MG should default to use some other mode on all subsequent buffers that are +created. This command is a toggle. With no prefix argument, if the named +mode is not already on the list of default modes, then it will be added to +the list; otherwise, it is removed from the list. + + +9.1 No Tab Mode + +In notab mode, tabs are expanded into spaces instead of inserted literally +into the buffer. Literal tab characters are displayed as ^I (much like +other control characters). These commands are available if MG is compiled +with the symbol NOTAB defined. (This mode is mainly for use on systems +such as PRIMOS that do not treat tab as a series of spaces.) + +no-tab-mode +This command is a toggle to control whether notab mode is in effect. + +space-to-tabstop +Insert enough spaces to move the cursor to the next tab stop. In notab + + + 27 + + + + + + + +mode, this function is bound to C-i. + + +9.2 Overwrite Mode + +Normally, when characters are inserted into the buffer, they are spliced +into the existing text. In overwrite mode, inserting a character causes +the character already at the cursor position to be replaced. This is +useful for editing pictures, tables, and the like. + +overwrite-mode +This command is a toggle which controls whether overwrite mode is in +effect. + +9.3 Auto Fill + +Fill mode causes newlines to be added automatically at word breaks when +text is added at the end of a line, extending past the right margin. Auto +fill is useful for editing text and documentation files. + +auto-fill-mode + +This command is a toggle which controls whether fill mode is in effect. + +insert-with-wrap +This command works like self-insert, except that it checks to see if the +cursor has passed the right margin. If so, it fills the line by inserting +a line break between words. This command is bound to SPC in fill mode. + +fill-paragraph M-q +Fill the paragraph containing the cursor. + +set-fill-column C-x f +Without a prefix argument, this command sets the right margin at the +current cursor column. If a prefix argument is supplied, it is used +instead as the line width. + + +9.4 Auto Indent + +Indent mode binds RET to newline-and-indent, so that each new line is +indented to the same level as the preceeding line. This mode is useful for +editing code. + + + + 28 + + + + + + + +auto-indent-mode +This command is a toggle which controls whether auto-indent mode is in +effect. + + +9.5 Blink + +Blink mode makes it easier to match parentheses, brackets, and other +paired delimiters. When the closing delimiter is typed, the cursor moves +momentarily to the matching opening delimiter (if it is on the screen), or +displays the line containing the matching delimiter on the echo line. This +is useful for editing Lisp or C code, or for preparing input files for text +processors such as LaTeX that use paired delimiters. + +blink-matching-paren +This command is a toggle which controls whether blink mode is in effect. + +blink-matching-paren-hack +This function behaves like self-insert, except that it finds the matching +delimiter as described above. In blink mode, this function is bound to ), +which flashes the matching (. This function also knows about the pairs {}, +[], and <>. All other characters match with themselves. + + +9.6 Dired Mode + +``Dired'' is an abbreviation for ``directory editor'', and it provides a +way to browse through the contents of a directory from with MG. Dired puts +a directory listing into a buffer; you can use normal editing commands to +move around the buffer, and a special group of commands to manipulate the +files. For example, there are commands to delete and rename files, and to +read a file into an MG buffer. + Since dired mode rebinds many keys, a table may be helpful: + + C-d dired-flag-file-deleted + SPC next-line + c dired-copy-file + d dired-flag-file-deleted + e dired-find-file + f dired-find-file + n next-line + o dired-find-file-other-window + p previous-line + r dired-renamefile + + + 29 + + + + + + + + u dired-unflag + x dired-do-deletions + DEL dired-backup-unflag + + The commands in this section are disabled by defining NODIRED. + +dired directory-name C-x d +Creates a dired buffer for the given directory name, and displays it in +the current window. The files in the directory are listed, usually along +with information about the file such as its size and timestamp. The exact +format of the information is system-specific. + +dired-backup-unflag + +This function removes the deletion flag from the file listed on the +previous line of the dired buffer. + +dired-copy-file new-name +Copy the file listed on the current line of the dired buffer. + +dired-do-deletions +Deletes the files that have been flagged for deletion. + +dired-find-file +dired-find-file-other-window +These function works like find-file and find-file-other-window, except that +the filename is taken from the current line in the dired buffer. + +dired-flag-file-deleted + +Flag the file listed on the current line for deletion. This is indicated +in the buffer by putting a ``D'' at the left margin. No files are not +actually deleted until the function dired-do-deletions is executed. + +dired-other-window directory-name +This function works just like dired, except that it puts the dired buffer +in the ``other'' window. + +dired-rename-file new-name +Renames the file listed on the current line of the dired buffer. Note that +the dired buffer is not updated to reflect the change. + +dired-unflag +Remove the deletion flag for the file on the current line. + + + 30 + + + + + + + + + + + + + +Chapter 10 + + + + +Miscellaneous + + + +10.1 Help + +Most of the commands in this section write useful information to the *help* +buffer, which is then displayed in the ``other'' window. + These commands can be disabled at compile-time by defining NOHELP. + +apropos topic C-h a + +This command lists all functions whose names contain a string matching +topic in the *help* buffer. + +describe-bindings C-h b +Information about the key bindings in effect in the current buffer is +listed in the *help* buffer. + +describe-key-briefly key C-h c +Information about the binding of key is printed in the minibuffer. + +help-help option C-h C-h +This command lists all of the help options available and prompts for which +one to run. Currently, these include only a to run apropos, b to run +describe-bindings, and c to run describe-key-briefly. + + +10.2 Keyboard Macros + +A keyboard macro is a saved set of commands from the keyboard that can be +reexecuted later on. There can only be one keyboard macro defined at any +one time. + + + 31 + + + + + + + + The commands in this section are available unless they have been +disabled by defining NOMACRO. + +call-last-kbd-macro C-x e +Execute the saved keyboard macro. A prefix argument can be used to specify +a repetition count. + +end-kbd-macro C-x ) +start-kbd-macro C-x ( + +These functions are used to define a keyboard macro. All keys entered +after start-kbd-macro is executed, up to a end-kbd-macro, are remembered as +they are executed. You can then reexecute the same sequence of operations +using call-last-kbd-macro. + +10.3 Changing Case + +MG provides a number of functions for changing the case of text. + +capitalize-word M-c +downcase-region C-x C-l +downcase-word M-l +upcase-region C-x C-u +upcase-word M-u +All of these commands do the obvious. + + +10.4 Odds and Ends + +This section describes miscellaneous commands that don't fit into any +particular category. + +emacs-version +Prints information about the version of MG you are running in the +minibuffer. + +meta-key-mode +If the particular version of MG you are running supports a meta key, this +function can be used to determine whether MG actually pays attention to it +or not. If no prefix argument is supplied, the internal variable that +controls the use of the meta key is toggled; a positive value enables the +meta key, while a negative value disables it. + + + + + 32 + + + + + + + +prefix-region +set-prefix-string string +Prefix-region is used to prefix each line of the region with a string. +This is useful for indenting quoted text, making block comments, and the +like. The function set-prefix-string can be used to set the string used as +the prefix. + +suspend-emacs C-z + +This command temporarily suspends MG so that you can run other programs, +and later resume editing. The exact behavior depends on which operating +system you are running MG under. Typically, MG will either spawn a new +shell as a subprocess, or return you to the parent process. + +transpose-chars C-t +This command transposes the previous two characters. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 33 + + + + + + + + + + + + + +Chapter 11 + + + + +Customization + + + +MG provides a limited support for customization. However, unlike ``real'' +Emacs, there is no extension language for interpretively defining new +functions. + +11.1 Key Bindings + +MG allows keys to be rebound locally or globally. To understand the +difference between the two, some discussion on how modes are implemented is +necessary. + An internal data structure called a keymap is used to look up the +function that is bound to a particular key. The keymap for fundamental +mode contains all of the default bindings which are listed with the command +descriptions in this manual. Modes define additional keymaps that are +searched for a binding before the fundamental mode keymap is examined. +Keymaps have the same name as the mode they are associated with. + MG does not provide commands for defining new modes, but you can alter +the keymaps for existing modes. + +define-key keymap-name key command + +This command can be used to modify the keymap for the named mode. + +global-set-key key command +global-unset-key key +These commands modify the keymap for fundamental mode. Bindings +established by global-set-key will be inherited by all other modes, as long +as they do not establish local rebindings of the same key. + +local-set-key key command + + + 34 + + + + + + + +local-unset-key key +These commands modify the keymap currently in effect. + + +11.2 Startup Files + +Although MG does not include a general-purpose extension language, it does +provide a way to read and evaluate commands using a somewhat different +syntax than that used for executing extended commands. This is typically +used in a startup file to modify key bindings. + A startup file consists of one or more expressions. Each expression +must appear on a separate line in the file; there may not be more than +one expression per line, nor may expressions span across line breaks. +Whitespace (spaces and tabs) separate the tokens in an expression. For +historical reasons, parentheses are also considered to be whitespace in +this context. A semicolon acts as a comment character, causing the rest of +the line to be discarded. + An expression consists of a function name, an optional prefix argument +(given as an integer constant), and arguments to be passed to the function. +If an argument includes literal whitespace or nonprintable characters (for +example, as in a keystroke argument to one of the key binding functions +described in the previous section), it must be supplied as a string +constant enclosed in double quotes. + Within string constants, the following backslash escapes are available +to specify nonprintable characters: + +nt, nT Tab + +nn, nN Newline + +nr, nR Carriage return + +ne, nE Escape (Meta prefix) + +n^ Control prefix + +nn Specifies a character by its ASCII code, where n may consist + of from one to three octal digits + +nfn, nFn Specifies the keycode for the nth function key. N may + consist of one or two decimal digits. + + The following commands which deal with evaluation of expressions are +disabled by defining the compile-time option NOSTARTUP. + + + 35 + + + + + + + + The Rutgers Sun version will attempt to read two different startup +files, a general startup file and a terminal-specific startup file. +The terminal-specific startup file is intended primarily to define the +keypad. The general startup file is .mg in your home directory. +If there is no such file, /usr/local/lib/mg/mg will be used. The +terminal-specific startup file is .mg-TYPE, where TYPE represents the name +of the terminal type. E.g if your terminal type is set to vt100, MG +will read a file .mg-vt100. If there is no such file, it will try +/usr/local/lib/mg/mg-vt100. Files should exist in /usr/local/lib/mg for +the terminal types commonly in use at Rutgers. + For other versions, see the implementation notes for your particular +version of MG for information on how it handles startup files. + +eval-current-buffer +Evaluate the expressions in the current buffer. + +eval-expression expression + +Evaluate the expression supplied. + +load file-name +Read in the specified file and evaluate its contents. + + + + + + + + + + + + + + + + + + + + + + + + + 36 + + + + + + + + +Fundamental Mode Key Bindings +NUL set-mark-command C-x C-o delete-blank-lines +C-a beginning-of-line C-x C-s save-buffer +C-b backward-char C-x C-u upcase-region +C-d delete-char C-x C-w write-file +C-e end-of-line C-x C-x exchange-point-and-mark +C-f forward-char C-x ( start-kbd-macro +C-g keyboard-quit C-x ) end-kbd-macro +C-h help C-x 0 delete-window +TAB self-insert-command C-x 1 delete-other-windows +C-j newline-and-indent C-x 2 split-window-vertically +C-k kill-line C-x 4 c-x 4 prefix +C-l recenter C-x = what-cursor-position +RET newline C-x ^ enlarge-window +C-n next-line C-x b switch-to-buffer +C-o open-line C-x d dired +C-p previous-line C-x e call-last-kbd-macro +C-q quoted-insert C-x f set-fill-column +C-r isearch-backward C-x i insert-file +C-s isearch-forward C-x k kill-buffer +C-t transpose-chars C-x o other-window +C-u universal-argument C-x s save-some-buffers +C-v scroll-up C-x 4 C-f find-file-other-window +C-w kill-region C-x 4 C-g keyboard-quit +C-x c-x prefix C-x 4 b switch-to-buffer-other- +C-y yank window +C-z suspend-emacs C-x 4 f find-file-other-window +ESC meta prefix +SPC .. ~ self-insert-command M-C-g keyboard-quit +DEL delete-backward-char M-C-v scroll-other-window + M-SPC just-one-space +C-h C-g keyboard-quit M-% query-replace +C-h C-h help-help M-- negative-argument +C-h a apropos M-0 digit-argument +C-h b describe-bindings M-1 digit-argument +C-h c describe-key-briefly M-2 digit-argument + M-3 digit-argument +C-x C-b list-buffers M-4 digit-argument +C-x C-c save-buffers-kill-emacs M-5 digit-argument +C-x C-f find-file M-6 digit-argument +C-x C-g keyboard-quit M-7 digit-argument +C-x C-l downcase-region M-8 digit-argument + + + + 37 + + + + + + + +M-9 digit-argument +M-< beginning-of-buffer +M-> end-of-buffer +M-[ backward-paragraph +M-\ delete-horizontal-space +M-] forward-paragraph +M-b backward-word +M-c capitalize-word +M-d kill-word +M-f forward-word +M-l downcase-word +M-q fill-paragraph +M-r search-backward +M-s search-forward +M-u upcase-word +M-v scroll-down +M-w copy-region-as-kill +M-x execute-extended-command +M-~ not-modified +M-DEL backward-kill-word + + + + + + + + + + + + + + + + + + + + + + + + + + + 38 + + + + + + + + + + + +Index + + + +apropos ....................... 31 dired-other-window ............ 30 +auto-fill-mode ................ 27 dired-rename-file ............. 30 +auto-indent-mode .............. 28 dired-unflag .................. 30 +backward-char .................. 9 downcase-region ............... 32 +backward-kill-word ............ 14 downcase-word ................. 32 +backward-paragraph ............. 9 emacs-version ................. 32 +backward-word .................. 9 end-kbd-macro ................. 32 +beginning-of-buffer ............ 9 end-of-buffer .................. 9 +beginning-of-line .............. 9 end-of-line ................... 10 +blink-matching-paren .......... 28 enlarge-window ................ 21 +blink-matching-paren-hack ..... 28 eval-current-buffer ........... 36 +call-last-kbd-macro ........... 32 eval-expression ............... 36 +capitalize-word ............... 32 exchange-point-and-mark ....... 10 +cd ............................ 25 execute-extended-command ....... 8 +copy-region-as-kill ........... 14 fill-paragraph ................ 27 +count-matches ................. 19 find-file ..................... 24 +count-non-matches ............. 19 find-file-other-window ........ 24 +define-key .................... 34 forward-char .................. 10 +delete-backward-char .......... 14 forward-paragraph ............. 10 +delete-blank-lines ............ 14 forward-word .................. 10 +delete-char ................... 14 global-set-key ................ 34 +delete-horizontal-space ....... 15 global-unset-key .............. 34 +delete-matching-lines ......... 19 goto-line ..................... 10 +delete-non-matching-lines ..... 19 help-help ..................... 31 +delete-other-windows .......... 21 insert ........................ 12 +delete-window ................. 21 insert-buffer ................. 23 +describe-bindings ............. 31 insert-file ................... 24 +describe-key-briefly .......... 31 insert-with-wrap .............. 27 +digit-argument ................. 8 isearch-backward .............. 16 +dired ......................... 29 isearch-forward ............... 16 +dired-backup-unflag ........... 29 just-one-space ................ 15 +dired-copy-file ............... 29 keyboard-quit .................. 8 +dired-do-deletions ............ 29 kill-buffer ................... 23 +dired-find-file ............... 29 kill-line ..................... 15 +dired-find-file-other-window .. 29 kill-paragraph ................ 15 +dired-flag-file-deleted ....... 29 kill-region ................... 15 + + + 39 + + + + + + + +kill-word ..................... 15 set-prefix-string ............. 33 +list-buffers .................. 23 shrink-window ................. 22 +load .......................... 36 space-to-tabstop .............. 26 +local-set-key ................. 34 split-window-vertically ....... 22 +local-unset-key ............... 35 start-kbd-macro ............... 32 +make-backup-files ............. 25 suspend-emacs ................. 33 +meta-key-mode ................. 32 switch-to-buffer .............. 24 +negative-argument .............. 8 switch-to-buffer-other-window . 24 +newline ....................... 12 transpose-chars ............... 33 +newline-and-indent ............ 12 universal-argument ............. 8 +next-line ..................... 10 upcase-region ................. 32 +no-tab-mode ................... 26 upcase-word ................... 32 +not-modified .................. 24 what-cursor-position .......... 11 +open-line ..................... 12 write-file .................... 25 +other-window .................. 21 yank ......................... 15 +overwrite-mode ................ 27 +prefix-region ................. 33 +previous-line ................. 10 +previous-window ............... 22 +pwd ........................... 25 +query-replace ................. 17 +query-replace-regexp .......... 19 +quoted-insert ................. 12 +re-search-again ............... 20 +re-search-backward ............ 20 +re-search-forward ............. 20 +recenter ...................... 10 +redraw-display ................ 11 +save-buffer ................... 24 +save-buffers-kill-emacs ....... 24 +save-some-buffers ............. 25 +scroll-down ................... 11 +scroll-one-line-down .......... 11 +scroll-one-line-up ............ 11 +scroll-other-window ........... 11 +scroll-up ..................... 11 +search-again .................. 17 +search-backward ............... 17 +search-forward ................ 17 +self-insert-command ........... 13 +set-case-fold-search .......... 20 +set-default-mode .............. 26 +set-fill-column ............... 27 +set-mark-command .............. 11 + + + 40 +
\ No newline at end of file diff --git a/usr.bin/mg/mg.dvi b/usr.bin/mg/mg.dvi Binary files differnew file mode 100644 index 00000000000..427c39c48ea --- /dev/null +++ b/usr.bin/mg/mg.dvi diff --git a/usr.bin/mg/mg.tex b/usr.bin/mg/mg.tex new file mode 100644 index 00000000000..75159922c4d --- /dev/null +++ b/usr.bin/mg/mg.tex @@ -0,0 +1,1345 @@ +%\batchmode %be nice to fellow VAXers +\hoffset=0in \voffset=0in %correct dvi2ln3 bug +\documentstyle[11pt,titlepage,dvidoc]{report} +%\pagestyle{headings} %not sure on this one yet +%\parindent=0in +\textwidth=5.5in +%\oddsidemargin=0.25in + +% definitions for DEFINE environment +\newlength{\lblwide} +\newcommand{\resetmarg}{\settowidth{\leftmargin}{\makebox[\lblwide]{}\makebox[\labelsep]{}}} +\newcommand{\resetlbl}{\setlength{\labelwidth}{\lblwide}} +\newenvironment{define}[1]{\begin{list}{}{\settowidth{\lblwide}{\bf #1}\resetmarg \resetlbl}}{\end{list}} + +% definitions for fn environment + +\newcommand{\fname}[3]{\hspace{1em}\\{\tt #1}\index{#1} {\it #2}\hfill{\tt #3}} +\newcommand{\fmore}[3]{\\{\tt #1}\index{#1} {\it #2}\hfill{\tt #3}} +\newcommand{\fbody}{\\[0.5em]} + +% definitions for making the index +\newcommand{\indexentry}[2]{\item {\tt #1} \dotfill\ #2} + + +\setcounter{secnumdepth}{5} %personal preference +\setcounter{tocdepth}{5} %personal preference +\author{Sandra J. Loosemore} +\title{The MG Reference Manual \\ Release MG2A} +\date{\vspace{1.5in} +Copyright \copyright 1987, Sandra J. Loosemore \\[0.5em] +\parbox{5 in}{This document, or sections of this document, may be freely +redistributed provided that the copyright notice and the following disclaimer +remain intact: The author bears no responsibilities for errors in +this document or the software it describes; and shall not be held liable +for any indirect, incidental, or consequential damages.}} + +\makeindex %usually not + +\begin{document} +\maketitle + +\tableofcontents + +\chapter{Introduction} + +MG is a small, fast, and portable Emacs-style text editor intended to +be used by people who can't run a real Emacs for one reason or another +--- as their main editor on smaller machines with limited memory or +file space, or as a ``quick-start'' editor on larger systems, useful +for composing short mail messages and the like. + +We've made MG compatible with GNU Emacs because that is the ``big'', +full-featured editor that many of us use regularly and are most +familiar with. GNU Emacs is the creation of Richard M. Stallman, who +was also the author of the original Emacs editor. However, MG is not +associated in any way with the GNU project, and the MG authors +individually may or may not agree with the opinions expressed by +Richard Stallman and the GNU project. + +MG is largely public domain. You can use, modify, and redistribute MG +as you like. A few modules, however, are copyrighted; specifically, +the regular expression code, the VMS termcap routines, and the Amiga +support code. Look at the source code for the exact copyright +restrictions. + +There are several other editors in existence which call themselves +MicroEmacs. The original public domain version was written by Dave +Conroy and circulated as version 1.6. Derived from this, there is +another PD version by Dave Conroy numbered v30; a significantly larger +PD version by Daniel Lawrence which is now up to version 3.9; at least +one proprietary implementation; an implementation for the Atari ST +with an integrated command shell, by Prabhaker Mateti; and probably +others that we don't know about. + +MG is derived from the v30 MicroEmacs, with key bindings, command +names, and general functionality made more compatible with GNU Emacs. +Like v30, MG is fairly small and quite robust. We have generally +resisted the temptation to overfeaturize. Some features which are +large and complex are flagged for conditional compilation. + +Many people have contributed their time to developing, improving, and +porting MG. Mike Meyer, Mic Kaczmarczik, and Bob Larson deserve +particular mention for their efforts. + +Questions, suggestions, and offers of help should be addressed to: + +\begin{verbatim} + mg-developers@ucbvax.berkeley.edu (ARPA) + ucbvax!mg-developers (UUCP) +\end{verbatim} + +\section{Implementations of MG} + +MG runs on many different kinds of hardware under many different +operating systems. Currently, these include: + +\begin{itemize} + +\item 4.2 and 4.3 BSD Unix (including Ultrix-32) +\item System V Unix +\item VAX/VMS +\item Primos +\item OS9/68k +\item Amiga +\item Atari ST +\item MS-DOS + +\end{itemize} + +This document describes release MG2A. When we talk of different +versions of MG in this manual, the term {\em version\/} is used to +refer to the different support MG provides for the various machines +and operating systems it runs under, not to different releases of MG +itself. For example, we might speak of how the VMS version of MG +differs from the Unix version. + +As mentioned above, some MG commands may not be implemented in all +versions; these are noted in the documentation. Some versions of MG +also support features (such as mouse handling) that are not described +here. + +\section{A Note on Character Sets} + +MG uses the 128-character ASCII character set, and provides support for +8-bit characters. Whether the particular version of MG that you are running +knows about extended character sets depends on whether your terminal and +the host operating system know about them. Moreover, since there is no +standard 8-bit character set, the same character codes will probably +give different glyphs on different systems. Most versions of MG use +the DEC multinational character set. + +\section{Notation and Conventions} + +In this manual, commands and other things that must be typed in +literally are indicated in a typewriter font, like {\tt next-line}. +Placeholders such as command argument names use an italic font. + +The terms {\em command\/} and {\em function\/} are synonymous. We often +speak of a command being bound to a particular {\em key\/}, although you +may actually have to type more than one character to form a single key. +Most commands are bound to keys with {\em control\/} and {\em meta\/} +modifiers. + +To type a control character, use the control key on your +keyboard like a shift key: hold down the control key while typing the +character. In this manual, we will indicate control characters like +\verb"C-x" --- here, typing the character ``x'' while holding down +the control key. + +Some keyboards also have a meta key that works like the control +key. (It may be labelled something else; on the Atari ST, for example, +the key marked ``Alternate'' is the meta key.) If your keyboard doesn't +have a meta key, don't panic. You can also use the escape key as a meta +prefix; first type the escape, and {\em then\/} the character. Meta +characters will be indicated as \verb"M-x". + +Besides the meta prefix, two other characters are used as prefixes: +\verb"C-x" and \verb"C-h". A few keys have special notation: {\tt SPC} is +the space character, {\tt DEL} is the delete or rubout character, {\tt RET} +is carriage return, and {\tt ESC} is the escape character. {\tt NUL} is +the null character (ASCII 0), which is usually equivalent to either +\verb"C-SPC" or \verb"C-@". + +Uppercase and lowercase characters are generally equivalent in command +keystrokes. + +When you run MG from a shell, command line arguments are interpreted as the +names of files you want to {\em visit\/}, or edit. Each file is +read into a {\em buffer\/} in memory. No changes are actually made to +the file until you ask it to be written out to disk. + +Within MG, the large top part of the screen serves as a {\em window\/} into +the buffer being edited. Below this is the {\em mode line\/}, which +displays the name of the buffer. Finally, at the very bottom of the screen, +there is a one-line {\em minibuffer\/} which is used for displaying +messages and answering questions. + +MG keeps track of two pointers into each window, the {\em point\/} and the +{\em mark\/}. The {\em cursor\/} appears at the point in the current +window, and we often speak of moving the cursor rather than of moving the +point. The text between the point and the mark is referred to as the {\em +region\/}. + +Some commands deal with {\em words\/} and {\em paragraphs\/}. +Generally, whitespace and punctuation separate words. Lines that are +empty or that contain only spaces or tabs separate paragraphs without +being part of a paragraph. A non-empty line that starts with a space +or tab also begins a new paragraph. + +A number of commands are defined as {\em toggles\/}. If no prefix argument +is supplied, these commands toggle an action. The action is turned on if a +negative or zero argument is supplied, and turned on if a positive argument +is supplied. + +\section{Getting Started} + +This document is intended primarily as a reference manual. If you +have never used any Emacs-like text editor before, it is strongly +suggested that you run the on-line tutorial supplied with the MG +distribution, instead of reading this manual. + +Do not be put off by the large number of commands described in this +manual! It is possible to get by with only a handful of basic commands. +Here are the ones that are probably used most frequently: + +\begin{define}{\hspace{1in}} +\item[{\tt C-p}\hfill] Move the cursor to the previous line +\item[{\tt C-n}\hfill] Move the cursor to the next line +\item[{\tt C-b}\hfill] Move the cursor backwards +\item[{\tt C-f}\hfill] Move the cursor forwards +\item[{\tt C-v}\hfill] Scroll forwards one screenful +\item[{\tt M-v}\hfill] Scroll backwards one screenful +\item[{\tt M-<}\hfill] Go to the beginning of the buffer +\item[{\tt M->}\hfill] Go to the end of the buffer +\item[{\tt C-a}\hfill] Go to the beginning of the line +\item[{\tt C-e}\hfill] Go to the end of the line +\item[{\tt DEL}\hfill] Delete the previous character +\item[{\tt C-k}\hfill] Kill (delete) to the end of line +\item[{\tt C-y}\hfill] Reinsert killed text. +\item[{\tt C-x C-c}\hfill] Exit MG +\item[{\tt C-x C-s}\hfill] Save the current buffer +\end{define} + +\chapter{Using Commands} + +\section{Command Arguments} + +Some commands require arguments. For example, if you want to read a +file into a buffer, you must type in the name of the file. In the +descriptions of commands in this manual, if arguments are required, +they are listed following the command name. + +MG prompts for command arguments in the minibuffer. Within the minibuffer, +the following characters can be used for editing: + +\begin{define}{\hspace{1in}} +\item[{\tt DEL, C-h}\hfill] Erase the last character. +\item[{\tt C-x, C-u}\hfill] Erase the entire input line. +\item[{\tt C-w}\hfill] Erase to the beginning of the previous word. +\item[{\tt C-q, $\backslash$}\hfill] Quote the next character typed. +\item[{\tt RET}\hfill] Signifies that you have completed typing in +the argument. +\item[{\tt C-g}\hfill] Abort the command in progress. +\end{define} + +\section{Prefix Arguments} + +All commands accept an optional numeric prefix argument. This is +often interpreted as a repetition count. For example, the function +{\tt next-line}, if given a prefix argument, will move the cursor +forward that many lines; without an argument, it will move the cursor +forward one line. A few commands behave differently if given a prefix +argument than they do without one, and others ignore the prefix +argument entirely. + +\fname{digit-argument}{}{M-0, M-1, M-2, M-3, M-4, M-5, M-6, M-7, M-8, M-9} +\fmore{negative-argument}{}{M--} +\fbody One way to specify a command argument is to use the escape key +as a meta prefix, and then type one or more digits. A dash may be +used for a negative argument. + +\fname{universal-argument}{}{C-u} +\fbody Another way to specify a command prefix is to type {\tt C-u}. +Typing one {\tt C-u} is equivalent to a prefix argument of 4, typing +two gives a value of 16, and so on. In addition, you can type digits +following {\tt C-u} to form a numeric prefix argument. + +\section{Aborting} + +\fname{keyboard-quit}{}{C-g} +\fbody Typing {\tt C-g} cancels any command. It is particularly useful +for cancelling a command when MG is prompting for input in the minibuffer. + +\section{Extended Commands} + +\fname{execute-extended-command}{command}{M-x} +\fbody Commands that are not bound to keys can be executed through +{\tt execute extended-command}. If a prefix argument is supplied, it +is passed to the command being executed. + + +\chapter{Moving the Cursor} + +The commands described in this chapter move the cursor (sometimes +called the point or dot) within the current window. Commands which +set the mark are included here as well. + +\fname{backward-char}{}{C-b} +\fbody Moves the cursor backward (left) one character. If the cursor +is at the left margin, it will be moved to the end of the previous line. + +\fname{backward-paragraph}{}{M-[} +\fbody Moves the cursor backwards to the beginning of the current +paragraph, or to the beginning of the previous paragraph if the cursor +is already at the beginning of a paragraph. + +\fname{backward-word}{}{M-b} +\fbody Moves the cursor backwards to the beginning of the current word, +or to the beginning of the previous word if the cursor is already at +the beginning of a word. + +\fname{beginning-of-buffer}{}{M-<} +\fbody Moves the cursor backwards to the beginning of the buffer. + +\fname{beginning-of-line}{}{C-a} +\fbody Moves the cursor backwards to the beginning of the current +line. This command has no effect if the cursor is already at the beginning +of the line. + +\fname{end-of-buffer}{}{M->} +\fbody Moves the cursor forwards to the end of the buffer. + +\fname{end-of-line}{}{C-e} +\fbody Moves the cursor forwards to the end of the current line. This +command has no effect if the cursor is already at the end of the line. + +\fname{exchange-point-and-mark}{}{C-x C-x} +\fbody Set the mark at the current cursor position, and move the cursor +to the old location of the mark. + +\fname{forward-char}{}{C-f} +\fbody Moves the cursor forwards one character. If the cursor is at the +end of a line, it will be moved to the first character on the next line. + +\fname{forward-paragraph}{}{M-]} +\fbody Moves the cursor forwards to the next paragraph delimiter. + +\fname{forward-word}{}{M-f} +\fbody Moves the cursor forwards to the end of the current word, or to +the end of the next word if the cursor is already at the end of a word. + +\fname{goto-line}{line-number}{} +\fbody Moves the cursor to the beginning of line {\em line-number\/} in +the buffer. + +\fname{next-line}{}{C-n} +\fbody Moves the cursor down one line. The cursor remains in the same +column unless it would be past the end of the line, in which case it is +moved to the end of the line. At the end of the buffer, {\tt C-n} will +create new lines. + +\fname{previous-line}{}{C-p} +\fbody Moves the cursor up one line. The cursor remains in the same +column unless it would be past the end of the line, in which case it is +moved to the end of the line. + +\fname{recenter}{}{C-l} +\fbody Redraws the entire screen, scrolling the current window if necessary +so that the cursor is near the center. With a positive prefix argument +{\em n\/}, the window is scrolled so that the cursor is {\em n\/} lines +from the top. A negative prefix argument puts the cursor that many lines +from the bottom of the window. + +\fname{redraw-display}{}{} +\fbody Redraws the entire screen, but never scrolls. + +\fname{scroll-down}{}{M-v} +\fbody Scrolls the display down (moving backward through the +buffer). Without +an argument, it scrolls slightly less than one windowful. A prefix argument +scrolls that many lines. + +\fname{scroll-one-line-down}{}{} +\fmore{scroll-one-line-up}{}{} +\fbody These functions are similar to {\tt scroll-down} and {\tt scroll-up} +(respectively), but when invoked without an argument, cause the display +to scroll by one line only. These functions are enabled by defining the +compile-time option GOSMACS. + +\fname{scroll-other-window}{}{M-C-v} +\fbody Scrolls the ``other'' window forward as for {\tt scroll-up}. + +\fname{scroll-up}{}{C-v} +\fbody Scrolls the display up (moving forward through the buffer). Without an +an argument, it scrolls slightly less than one windowful. A prefix argument +scrolls that many lines. + +\fname{set-mark-command}{}{NUL} +\fbody Set the mark at the current cursor position. + +\fname{what-cursor-position}{}{C-x =} +\fbody Prints some information in the minibuffer about where the cursor is. + + +\chapter{Text Insertion Commands} + +The usual way to insert text into a buffer is simply to type the +characters. The default binding for all of the printing characters +({\tt self-insert-command}) causes them to be inserted literally at +the cursor position. + +\fname{insert}{string}{} +\fbody Insert {\em string\/} into the current buffer at the cursor position. + +\fname{newline}{}{RET} +\fbody Insert a line break into the current buffer at the cursor position, +moving the cursor forward to the beginning of the new line. + +\fname{newline-and-indent}{}{C-j} +\fbody Insert a line break into the current buffer at the cursor position, +then add extra whitespace so that the cursor is aligned in the same +column as the first non-whitespace character in the previous line. + +\fname{open-line}{}{C-o} +\fbody Inserts a line break into the current buffer at the current position, +but does not move the cursor forward. + +\fname{quoted-insert}{}{C-q} +\fbody This command acts as a prefix to +cancel the normal interpretation of the next keystroke. If {\tt C-q} +is followed by one to three octal digits, it is interpreted as the +code of the character to insert. Otherwise a single key is read and +the character typed is inserted into the buffer instead of interpreted +as a command. This is used for inserting literal control characters +into a buffer. + +\fname{self-insert-command}{}{} +\fbody This is the default binding for keys representing printable +characters. The character is inserted into the buffer at the cursor +position, and the cursor moved forward. + +\chapter{Killing, Deleting, and Moving Text} + +When text is deleted, it is erased completely. Killing text, on the +other hand, moves it into a temporary storage area called the kill +buffer. The saved text in the kill buffer is erased when another +block of text is killed. Until then, however, you can retrieve text +from the kill buffer. This can be used to move or copy blocks of +text, as well as to restore accidentally killed text. + +\fname{backward-kill-word}{}{M-DEL} +\fbody Kill the text backwards from the cursor position to the beginning +of the current word. Typing {\tt M-DEL} several times in succession +prepends each killed word to the kill buffer. + +\fname{copy-region-as-kill}{}{M-w} +\fbody Copies the text in the region into the kill buffer, without removing +it from the current buffer. + +\fname{delete-backward-char}{}{DEL} +\fbody Deletes the character to the left of the cursor. + +\fname{delete-blank-lines}{}{C-x C-o} +\fbody Deletes all blank lines after the current line, and if the current +line is blank, deletes it and all blank lines preceeding it as well. + +\fname{delete-char}{}{C-d} +\fbody Deletes the character underneath the cursor. + +\fname{delete-horizontal-space}{}{M-$\backslash$} +\fbody Deletes all spaces and tabs on either side of the cursor. + +\fname{just-one-space}{}{M-SPC} +\fbody This is like {\tt delete-horizontal-space}, except it leaves a single +space at the cursor position. + +\fname{kill-line}{}{C-k} +\fbody If no prefix argument is specified, this function kills text up +to the next newline; or if the cursor is at the end of a line, the newline +is killed. A prefix argument specifies how many lines to kill. Typing +{\tt C-k} several times in succession appends each line to the kill buffer. + +\fname{kill-paragraph}{}{} +\fbody This command kills the entire paragraph containing the cursor. +If the cursor is positioned between paragraphs, the next paragraph is killed. + +\fname{kill-region}{}{C-w} +\fbody The region (all text between point and mark) is killed. + +\fname{kill-word}{}{M-d} +\fbody Text is killed forward from the cursor position to the next +end of word. If the cursor is at the end of the word, then the next +word is killed. Typing {\tt M-d} several times appends the killed +text to the kill buffer. + +\fname{yank}{}{C-y} +\fbody Text is copied from the kill buffer into the current buffer at +the cursor position. The cursor is moved to the end of the inserted +text. + +\chapter{Searching and Replacing} + +\section{Searching} + +The ordinary search command in MG differs from that in many other editors +in that it is incremental: it begins searching as soon as you begin +typing the search string, instead of waiting for you to type the entire +string. + +All of the search commands described in this section are case-insensitive. + + +\fname{isearch-backward}{pattern}{C-r} +\fmore{isearch-forward}{pattern}{C-s} +\fbody These commands perform an incremental search backward and +forward (respectively) for {\em pattern\/}. MG will move the cursor +to the place in the buffer that matches as much of the pattern as you +have typed so far, as each character is entered. + + +Within the incremental search, the following characters are interpreted +specially: + +\begin{define}{\hspace{1in}} + +\item[{\tt DEL}\hfill] Erase the last character in the search string. + +\item[{\tt ESC}\hfill] Stop searching; exit from incremental search +mode, leaving the cursor where the search brought it. + +\item[{\tt C-g}\hfill] If a match has been found, exits from +incremental search but leaves the cursor in its original position. If +the search has failed, this will just erase the characters which have +not been found from the end of the search pattern. In this case, you +must type \verb"C-g" again to abort the search. + +\item[{\tt C-s}\hfill] Search forward for the next occurrence of the +same pattern. + +\item[{\tt C-r}\hfill] Search backward for the previous occurrence of +the same pattern. + +\item[{\tt C-q}\hfill] ``Quotes'' the next character typed, forcing it +to be interpreted as a literal character in the search pattern. +\end{define} + +In addition, normal commands such as \verb"C-a" that do not have special +meanings within incremental search cause the search to be terminated, and +then are executed in the ordinary way. + +\fname{search-again}{}{} +\fmore{search-backward}{pattern}{M-r} +\fmore{search-forward}{pattern}{M-s} +\fbody These commands perform ordinary, non-incremental searches. +{\tt Search-again} uses the same pattern and direction as the previous +search. + +\section{Replacing} + +\fname{query-replace}{pattern replacement}{M-\%} +\fbody The primary replace command in MG is an interactive query replace. +MG searches forward for occurrences of {\em pattern\/}, and asks you what +to do about each one. The choices are: + +\begin{define}{\hspace{1in}} + +\item[{\tt SPC}\hfill] Replace this match with {\em replacement\/}, +and go on to the next. + +\item[{\tt DEL}\hfill] Skip to the next match without replacing this one. + +\item[{\tt .}\hfill] Replace this match, and then quit. + +\item[{\tt !}\hfill] Replace all remaining occurrences without asking again. + +\item[{\tt ESC}\hfill] Quit. +\end{define} + +By default, {\tt query-replace} adjusts the case of lower-case letters +in the replacement string to match that of the +particular occurrence of the pattern; for example, replacing ``Foo'' +with ``bar'' results in ``Bar''. Upper case letters in the replacement +string are always left uppercase. In addition, supplying a prefix argument +will also tell {\tt query-replace} to leave the case of the replacement +string as-is. + +Note that {\tt query-replace} always performs a case-insensitive search. + + + +\section{Regular Expressions} + +Regular expressions provide a means for specifying complex search +patterns, instead of just a literal string. The commands in this +section are available only if MG is compiled with the REGEX option +defined. + +Regular expression syntax uses the following rules. Most characters +in a regular expression are considered to be {\em ordinary\/} characters, +and will match themselves and nothing else. The exceptions are the +special characters listed below. + +\begin{define}{\hspace{1in}} + +\item[{\tt .}\hfill] Matches any single character except a newline. + +\item[{\tt *}\hfill] A suffix operator that matches zero or more +repetitions of the (smallest) preceding regular expression. + +\item[{\tt +}\hfill] A suffix operator that matches one or more +repetitions of the (smallest) preceding regular expression. + +\item[{\tt ?}\hfill] A suffix operator that matches either zero or one +occurence of the (smallest) preceding regular expression. + +\item[{\tt [\ldots]}\hfill] Matches any one character listed in the +character set between the square brackets. See examples below. + +\item[{\tt \^{ }}\hfill] Matches at the beginning of a line. + +\item[{\tt \$}\hfill] Matches at the end of a line. + +\item[{\tt $\backslash$}\hfill] Except for the situations listed +below, acts as a prefix operator which causes the character following +to be treated as an ordinary character. + +\item[{\tt $\backslash$|}\hfill] An infix binary {\em or\/} operator. +It applies to the two largest surrounding expressions. + +\item[{\tt $\backslash$(\ldots$\backslash$)}\hfill] A grouping construct, +usually used to specify a larger expression for postfix operators such +as \verb"*" or to limit the scope of operands to \verb"\|". + +\item[{\tt $\backslash${\em digit\/}}\hfill] Matches the same text +matched by the {\em digit\/}th \verb"\(...\)" construct. These are +numbered from 1 to 9 in the order that the open-parentheses appear. + +\item[{\tt $\backslash$`}\hfill] Matches at the beginning of the buffer. + +\item[{\tt $\backslash$'}\hfill] Matches at the end of the buffer. + +\item[{\tt $\backslash$b}\hfill] Matches at the beginning or end of a word. + +\item[{\tt $\backslash$B}\hfill] Matches anyplace {\em except\/} at +the beginning or end of a word. + +\item[{\tt $\backslash$<}\hfill] Matches at the beginning of a word. + +\item[{\tt $\backslash$>}\hfill] Matches at the end of a word. + +\item[{\tt $\backslash$w}\hfill] Matches any word-constituent character. + +\item[{\tt $\backslash$W}\hfill] Matches any character which is {\em +not\/} a word-constituent. + +\end{define} + +Some examples may help clarify the rules. + +\begin{define}{\hspace{1in}} + +\item[{\tt foo}\hfill] Matches the literal string {\tt foo}. + +\item[{\tt ;.*}\hfill] Matches all strings which begin with a +semicolon and continue to the end of a line. + +\item[{\tt c[ad]+r}\hfill] Matches strings of the form {\tt car}, {\tt +cdr}, {\tt caar}, {\tt cadr}, and so on. + +\item[{\tt [a-z]}\hfill] Matches any lowercase letter. + +\item[{\tt [\^{ }a-z]}\hfill] Matches any character {\em except\/} +lowercase letters. + +\item[{\tt [0-9+---]}\hfill] Matches a digit or sign. + +\item[{\tt $\backslash$(foo$\backslash$|bar$\backslash$)}\hfill] +Matches either the string {\tt foo} or the string {\tt bar}. + +\end{define} + +\fname{count-matches}{pattern}{} +\fmore{count-non-matches}{pattern}{} +\fbody These commands count the number of lines which do or do not +(respectively) match the specified pattern. + +\fname{delete-matching-lines}{pattern}{} +\fmore{delete-non-matching-lines}{pattern}{} +\fbody These commands delete all lines which do or do not (respectively) +match the specified pattern. + + +\fname{query-replace-regexp}{pattern replacement}{} +\fbody This is the regular expression version of {\tt query-replace}. + +The {\em replacement\/} string may be a constant, or it can refer to +all or part of the string matched by the {\em pattern\/}. \verb"\&" in +the replacement string expands into the entire text being replaced, +while \verb"\"{\em n\/} (where {\em n\/} is a number) replaces the +{\em n\/}th parenthesized expression in {\em pattern\/}. + +\fname{re-search-again}{}{} +\fmore{re-search-backward}{pattern}{} +\fmore{re-search-forward}{pattern}{} +\fbody These are the regular expression equivalents of the ordinary +non-incremental search commands. + +\fname{set-case-fold-search}{}{} +\fbody This command toggles an internal variable that controls whether +the regular expression search and replace commands pay attention to +case. By default, regular expression searches are case-insensitive. +Ordinary searches are always case-insensitive and are not affected by +the setting of this variable. + + +\chapter{Windows} + +MG initially has only one text window displayed. However, you can have +as many windows as will fit on the screen. Each window has its own mode +line and must display at least two lines of text. (Note that a MG's +``windows'' are distinct from the ``windows'' handled by screen managers +such as the X Window System.) + +Multiple windows may be used to display different buffers. You can also +have the same buffer displayed in more than one window, which is useful +if you want to see one part of a file at the same time as you are editing +another part. + +Although many windows can be displayed at once, only one window is active +at any given time. This is the window where the cursor appears. + +Some commands refer to the ``other'' window. This is the window directly +below the current window, or the top window if you are in the bottom window. + +\fname{delete-other-windows}{}{C-x 1} +\fbody Makes the current window the only window. + +\fname{delete-window}{}{C-x 0} +\fbody Deletes the current window, making the ``other'' window the +current window. This command doesn't do anything useful if there is only +one window being displayed. + +\fname{enlarge-window}{}{C-\^{ }} +\fbody Makes the current window larger. Without a prefix argument, the +window grows one line; otherwise, the prefix argument specifies how many +lines to grow. + +\fname{other-window}{}{C-x o} +\fbody Makes the ``other'' window the current window. + +\fname{previous-window}{}{} +\fbody This is like {\tt other-window}, except that it cycles through +the windows in reverse order. This command is available only if MG was +compiled with the GOSMACS option defined. + +\fname{shrink-window}{}{} +\fbody Makes the current window smaller. Without a prefix argument, the +window loses one line; otherwise, the prefix argument specifies how many +lines go away. + +\fname{split-window-vertically}{}{C-x 2} +\fbody Split the current window into two windows, both using the same +buffer. + + +\chapter{Files and Buffers} + +Most buffers are used to contain a file being edited. It is +also possible to have buffers that are not associated with any file; +MG uses these for purposes such as displaying help text, for example. +However, since most commands for dealing with files also deal with +buffers, we have grouped all of these commands together into one chapter. + +\section{Buffer Manipulation} + +\fname{insert-buffer}{buffer-name}{} +\fbody Inserts the contents of the named buffer into the current buffer +at the cursor location. The cursor moves to the end of the inserted +text. + +\fname{kill-buffer}{buffer-name}{C-x k} +\fbody The named buffer and its contents are deleted. If the buffer has +been marked as modified, MG will ask you if you really want to delete it. +Note that, contrary to its name, this command {\em does not\/} save the +buffer contents in the kill buffer. + +If a buffer is being displayed in a window when it is deleted, MG will +find some other buffer to display in the same window. + +\fname{list-buffers}{}{C-x C-b} +\fbody This command writes information about the buffers currently in +use to a buffer named {\tt *Buffer List*}. This buffer is then displayed +in the ``other'' window; if there is only one window, this command will +split the screen into two windows. + +\fname{not-modified}{}{M-\~{ }} +\fbody This command makes MG think that the current buffer has not been +modified, even if it really has been changed. This affects the behavior +of the {\tt kill-buffer} and the buffer-saving commands described below. + +MG indicates modified buffers with two stars at the left end of the mode +line. + +\fname{switch-to-buffer}{buffer-name}{C-x b} +\fbody The current window is mapped onto the named buffer. If there +isn't already a buffer with that name around, MG will create one. + +\fname{switch-to-buffer-other-window}{buffer-name}{C-x 4 b} +\fbody This command works like {\tt switch-to-buffer}, except that the +``other'' window is used. If there is only one window, this command +splits the screen into two windows and maps the named buffer onto one +of them. + +\section{Reading and Writing Files} + +\fname{find-file}{file-name}{C-x f} +\fmore{find-file-other-window}{file-name}{C-x 4 C-f} +\fbody These commands are analagous to {\tt switch-to-buffer} and {\tt +switch-to-buffer-other-window}, respectively. The difference is that +these commands look for a buffer associated with the named file. If no +matching buffer is found, MG will create a new buffer with a name +derived from the filename, and attempt to read the file into the buffer. +If the named file cannot be opened, the buffer remains empty. + +\fname{insert-file}{file-name}{C-x i} +\fbody This command reads in the contents of the named file into the +current buffer at the cursor position. The cursor remains in the same +place. + +\fname{save-buffer}{}{C-x C-s} +\fbody If the current buffer has been modified, it is saved. Buffers +that are not associated with files cannot be written out with this +command. + +\fname{save-buffers-kill-emacs}{}{C-x C-c} +\fbody This command is used to leave MG and return control to the shell +or other program that was used to start MG. If there are modified buffers, +MG will ask you if you want to save them before exiting. + +\fname{save-some-buffers}{}{C-x s} +\fbody MG will ask you if you want to save modified buffers that are +associated with files. + +\fname{write-file}{file-name}{C-x C-w} +\fbody The current buffer is written out using the file name supplied. +This is useful for saving buffers that are not associated with files, or +for writing out a file with a different name than what was used to read +it in. + +\section{Backup Files} + +MG provides a way to save a copy of the original version of files which +have been modified and then written out again. The backup copy reflects +the state of the file as it existed the first time it was read into MG. +The name used for the backup file varies, depending on the operating +system. + +This feature is disabled if MG is compiled with NO\_BACKUP defined. + +\fname{make-backup-files}{}{} +\fbody This command is a toggle which +controls the state of an internal variable that determines whether MG +creates backup files. + +\section{Changing the Directory} + +The commands in this section are disabled by defining NO\_DIR. + +\fname{cd}{directory-name}{} +\fbody This command changes MG's notion of the ``current'' directory +or pathname. This is used to supply defaults for functions that read +or write files. + +The syntax for {\em directory-name\/} is obviously specific to the +particular operating system MG is running on. + +\fname{pwd}{}{} +\fbody Display what MG thinks is the current directory. + +\chapter{Modes} + +Modes are used to locally alter the bindings of keys on a +buffer-by-buffer basis. MG is normally in fundamental mode, and these +are the bindings that are listed with the command descriptions in +this manual. Modes define additional keymaps that are searched for +bindings before the fundamental mode bindings are examined; see the +section on key binding below for more details on how this works. + +\fname{set-default-mode}{mode-name}{} +\fbody Normally, when MG visits a file, it puts the associated buffer +into fundamental mode. Using the {\tt set-default-mode} command, you +can specify that MG should default to use some other mode on all subsequent +buffers that are created. This command is a toggle. With no prefix +argument, if the named mode is not already on the list of +default modes, then it will be added to the list; otherwise, it is removed +from the list. + +\section{No Tab Mode} + +In notab mode, tabs are expanded into spaces instead of inserted +literally into the buffer. Literal tab characters are displayed as +\verb"^I" (much like other control characters). These commands are +available if MG is compiled with the symbol NOTAB defined. (This mode +is mainly for use on systems such as PRIMOS that do not treat tab as a +series of spaces.) + +\fname{no-tab-mode}{}{} +\fbody This command is a toggle to control whether notab mode is in effect. + +\fname{space-to-tabstop}{}{} +\fbody Insert enough spaces to move the cursor to the next tab stop. In +notab mode, this function is bound to {\tt C-i}. + + +\section{Overwrite Mode} + +Normally, when characters are inserted into the buffer, they are spliced +into the existing text. In overwrite mode, inserting a character causes +the character already at the cursor position to be replaced. This is +useful for editing pictures, tables, and the like. + +\fname{overwrite-mode}{}{} +\fbody This command is a toggle which controls whether overwrite mode is +in effect. + +\section{Auto Fill} + +Fill mode causes newlines to be added automatically at word +breaks when text is added at the end of a line, extending past the +right margin. Auto fill is useful for editing text and documentation +files. + +\fname{auto-fill-mode}{}{} +\fbody This command is a toggle which controls whether fill mode is +in effect. + +\fname{insert-with-wrap}{}{} +\fbody This command works like {\tt self-insert}, except that it checks +to see if the cursor has passed the right margin. If so, it fills +the line by inserting a line break between words. This command is bound to +{\tt SPC} in fill mode. + +\fname{fill-paragraph}{}{M-q} +\fbody Fill the paragraph containing the cursor. + +\fname{set-fill-column}{}{C-x f} +\fbody Without a prefix argument, this command sets the right margin +at the current cursor column. If a prefix argument is supplied, it is used +instead as the line width. + +\section{Auto Indent} + +Indent mode binds {\tt RET} to {\tt newline-and-indent}, so +that each new line is indented to the same level as the preceeding +line. This mode is useful for editing code. + +\fname{auto-indent-mode}{}{} +\fbody This command is a toggle which controls whether auto-indent mode +is in effect. + +\section{Blink} + +Blink mode makes it easier to match parentheses, brackets, and other +paired delimiters. When the closing delimiter is typed, the cursor +moves momentarily to the matching opening delimiter (if it is on the +screen), or displays the line containing the matching delimiter on the +echo line. This is useful for editing Lisp or C code, or for +preparing input files for text processors such as LaTeX that use +paired delimiters. + +\fname{blink-matching-paren}{}{} +\fbody This command is a toggle which controls whether blink mode is +in effect. + +\fname{blink-matching-paren-hack}{}{} +\fbody This function behaves like {\tt self-insert}, except that it +finds the matching delimiter as described above. In blink mode, this +function is bound to \verb")", which flashes the matching \verb"(". This +function also knows about the pairs \verb"{}", \verb"[]", and \verb"<>". +All other characters match with themselves. + +\section{Dired Mode} + +``Dired'' is an abbreviation for ``directory editor'', and it provides a way +to browse through the contents of a directory from with MG. Dired puts +a directory listing into a buffer; you can use normal editing commands to +move around the buffer, and a special group of commands to manipulate +the files. For example, there are commands to delete and rename files, +and to read a file into an MG buffer. + +Since dired mode rebinds many keys, a table may be helpful: + +\begin{verbatim} + C-d dired-flag-file-deleted + SPC next-line + c dired-copy-file + d dired-flag-file-deleted + e dired-find-file + f dired-find-file + n next-line + o dired-find-file-other-window + p previous-line + r dired-renamefile + u dired-unflag + x dired-do-deletions + DEL dired-backup-unflag +\end{verbatim} + +The commands in this section are disabled by defining NO\_DIRED. + +\fname{dired}{directory-name}{C-x d} +\fbody Creates a dired buffer for the given directory name, and displays +it in the current window. The files +in the directory are listed, usually along with information about the +file such as its size and timestamp. The exact format of the information +is system-specific. + +\fname{dired-backup-unflag}{}{} +\fbody This function removes the deletion flag from the file listed on +the previous line of the dired buffer. + +\fname{dired-copy-file}{new-name}{} +\fbody Copy the file listed on the current line of the dired buffer. + +\fname{dired-do-deletions}{}{} +\fbody Deletes the files that have been flagged for deletion. + +\fname{dired-find-file}{}{} +\fmore{dired-find-file-other-window}{}{} +\fbody These function works like {\tt find-file} and +{\tt find-file-other-window}, except that the filename is taken +from the current line in the dired buffer. + +\fname{dired-flag-file-deleted}{}{} +\fbody Flag the file listed on the current line for deletion. This is +indicated in the buffer by putting a ``D'' at the left margin. No +files are not actually deleted until the function {\tt dired-do-deletions} +is executed. + +\fname{dired-other-window}{directory-name}{} +\fbody This function works just like {\tt dired}, except that it puts the +dired buffer in the ``other'' window. + +\fname{dired-rename-file}{new-name}{} +\fbody Renames the file listed on the current line of the dired buffer. +Note that the dired buffer is not updated to reflect the change. + +\fname{dired-unflag}{}{} +\fbody Remove the deletion flag for the file on the current line. + +\chapter{Miscellaneous} + +\section{Help} + +Most of the commands in this section write useful information to the +{\tt *help*} buffer, which is then displayed in the ``other'' window. + +These commands can be disabled at compile-time by defining NO\_HELP. + +\fname{apropos}{topic}{C-h a} +\fbody This command lists all functions whose names contain a string +matching {\em topic\/} in the {\tt *help*} buffer. + +\fname{describe-bindings}{}{C-h b} +\fbody Information about the key bindings in effect in the current buffer +is listed in the {\tt *help*} buffer. + +\fname{describe-key-briefly}{key}{C-h c} +\fbody Information about the binding of {\em key\/} is printed in the +minibuffer. + +\fname{help-help}{option}{C-h C-h} +\fbody This command lists all of the help options available and +prompts for which one to run. Currently, these include only {\tt a} +to run {\tt apropos}, {\tt b} to run {\tt describe-bindings}, and {\tt c} +to run {\tt describe-key-briefly}. + + +\section{Keyboard Macros} + +A keyboard macro is a saved set of commands from the keyboard that can be +reexecuted later on. There can only be one keyboard macro defined at +any one time. + +The commands in this section are available unless they have been disabled +by defining NO\_MACRO. + +\fname{call-last-kbd-macro}{}{C-x e} +\fbody Execute the saved keyboard macro. A prefix argument can be used +to specify a repetition count. + +\fname{end-kbd-macro}{}{C-x )} +\fmore{start-kbd-macro}{}{C-x (} +\fbody These functions are used to define a keyboard macro. All keys +entered after {\tt start-kbd-macro} is executed, up to a {\tt end-kbd-macro}, +are remembered as they are executed. You can then reexecute the same +sequence of operations using {\tt call-last-kbd-macro}. + + +\section{Changing Case} + +MG provides a number of functions for changing the case of text. + +\fname{capitalize-word}{}{M-c} +\fmore{downcase-region}{}{C-x C-l} +\fmore{downcase-word}{}{M-l} +\fmore{upcase-region}{}{C-x C-u} +\fmore{upcase-word}{}{M-u} +\fbody All of these commands do the obvious. + + +\section{Odds and Ends} + +This section describes miscellaneous commands that don't fit into any +particular category. + +\fname{emacs-version}{}{} +\fbody Prints information about the version of MG you are running in +the minibuffer. + +\fname{meta-key-mode}{}{} +\fbody If the particular version of MG you are running supports a meta key, +this function can be used to determine whether MG actually pays attention +to it or not. If no prefix argument is supplied, the internal variable +that controls the use of the meta key is toggled; a positive value enables +the meta key, while a negative value disables it. + +\fname{prefix-region}{}{} +\fmore{set-prefix-string}{string}{} +\fbody {\tt Prefix-region} is used to prefix each line of the region +with a string. This is useful for indenting quoted text, making block +comments, and the like. The function {\tt set-prefix-string} can be +used to set the string used as the prefix. + +\fname{suspend-emacs}{}{C-z} +\fbody This command temporarily suspends +MG so that you can run other programs, and later resume editing. The +exact behavior depends on which operating system you are running MG +under. Typically, MG will either spawn a new shell as a subprocess, or +return you to the parent process. + +\fname{transpose-chars}{}{C-t} +\fbody This command transposes the previous two characters. + + +\chapter{Customization} + +MG provides a limited support for customization. However, unlike ``real'' +Emacs, there is no extension language for interpretively defining new +functions. + +\section{Key Bindings} + +MG allows keys to be rebound locally or globally. To understand the +difference between the two, some discussion on how modes are implemented +is necessary. + +An internal data structure called a keymap is used to look up the +function that is bound to a particular key. The keymap for +fundamental mode contains all of the default bindings which are listed +with the command descriptions in this manual. Modes define additional +keymaps that are searched for a binding before the fundamental mode +keymap is examined. Keymaps have the same name as the mode they are +associated with. + +MG does not provide commands for defining new modes, but you can alter +the keymaps for existing modes. + +\fname{define-key}{keymap-name key command}{} +\fbody This command can be used to modify the keymap for the named mode. + +\fname{global-set-key}{key command}{} +\fmore{global-unset-key}{key}{} +\fbody These commands modify the keymap for fundamental mode. Bindings +established by {\tt global-set-key} will be inherited by all other modes, +as long as they do not establish local rebindings of the same key. + +\fname{local-set-key}{key command}{} +\fmore{local-unset-key}{key}{} +\fbody These commands modify the keymap currently in effect. + + +\section{Startup Files} + +Although MG does not include a general-purpose extension language, it +does provide a way to read and evaluate commands using a somewhat +different syntax than that used for executing extended commands. This +is typically used in a startup file to modify key bindings. + +A startup file consists of one or more expressions. Each expression must +appear on a separate line in the file; there may not be more than one +expression per line, nor may expressions span across line breaks. +Whitespace (spaces and tabs) separate the tokens in an expression. For +historical reasons, parentheses are also considered to be whitespace in +this context. A semicolon acts as a comment character, causing the rest +of the line to be discarded. + +An expression consists of a function name, an optional prefix argument +(given as an integer constant), and arguments to be passed to the +function. If an argument includes literal whitespace or nonprintable +characters (for example, as in a keystroke argument to one of the key +binding functions described in the previous section), it must be +supplied as a string constant enclosed in double quotes. + +Within string constants, the following backslash escapes are available +to specify nonprintable characters: + +\begin{define}{\hspace{1in}} + +\item[{\tt $\backslash$t, $\backslash$T}\hfill] Tab +\item[{\tt $\backslash$n, $\backslash$N}\hfill] Newline +\item[{\tt $\backslash$r, $\backslash$R}\hfill] Carriage return +\item[{\tt $\backslash$e, $\backslash$E}\hfill] Escape (Meta prefix) +\item[{\tt $\backslash$\^{ }}\hfill] Control prefix + +\item[{\tt $\backslash${\em n\/}}\hfill] Specifies a character by its +ASCII code, where {\em n\/} may consist of from one to three octal +digits + +\item[{\tt $\backslash$f{\em n\/}, $\backslash$F{\em n\/}}\hfill] +Specifies the keycode for the {\em n\/}th function key. {\em N\/} may +consist of one or two decimal digits. + +\end{define} + +The following commands which deal with evaluation of expressions are +disabled by defining the compile-time option NO\_STARTUP. + +The Rutgers Sun version will attempt to read two different startup +files, a general startup file and a terminal-specific startup file. +The terminal-specific startup file is intended primarily to define the +keypad. The general startup file is .mg in your home directory. If +there is no such file, /usr/local/lib/mg/mg will be used. The +terminal-specific startup file is .mg-TYPE, where TYPE represents the +name of the terminal type. E.g if your terminal type is set to vt100, +MG will read a file .mg-vt100. If there is no such file, it will try +/usr/local/lib/mg/mg-vt100. Files should exist in /usr/local/lib/mg +for the terminal types commonly in use at Rutgers. + +For other versions, see the implementation notes for your particular +version of MG for information on how it handles startup files. + + +\fname{eval-current-buffer}{}{} +\fbody Evaluate the expressions in the current buffer. + +\fname{eval-expression}{expression}{} +\fbody Evaluate the expression supplied. + +\fname{load}{file-name}{} +\fbody Read in the specified file and evaluate its contents. + +\twocolumn[\Huge{\vspace{2em}{\bf Fundamental Mode Key Bindings}\vspace{1.5em}}] +\addcontentsline{toc}{chapter}{Fundamental Mode Key Bindings} +\begin{verbatim} +NUL set-mark-command +C-a beginning-of-line +C-b backward-char +C-d delete-char +C-e end-of-line +C-f forward-char +C-g keyboard-quit +C-h help +TAB self-insert-command +C-j newline-and-indent +C-k kill-line +C-l recenter +RET newline +C-n next-line +C-o open-line +C-p previous-line +C-q quoted-insert +C-r isearch-backward +C-s isearch-forward +C-t transpose-chars +C-u universal-argument +C-v scroll-up +C-w kill-region +C-x c-x prefix +C-y yank +C-z suspend-emacs +ESC meta prefix +SPC .. ~ self-insert-command +DEL delete-backward-char + +C-h C-g keyboard-quit +C-h C-h help-help +C-h a apropos +C-h b describe-bindings +C-h c describe-key-briefly + +C-x C-b list-buffers +C-x C-c save-buffers-kill-emacs +C-x C-f find-file +C-x C-g keyboard-quit +C-x C-l downcase-region +C-x C-o delete-blank-lines +C-x C-s save-buffer +C-x C-u upcase-region +C-x C-w write-file +C-x C-x exchange-point-and-mark +C-x ( start-kbd-macro +C-x ) end-kbd-macro +C-x 0 delete-window +C-x 1 delete-other-windows +C-x 2 split-window-vertically +C-x 4 c-x 4 prefix +C-x = what-cursor-position +C-x ^ enlarge-window +C-x b switch-to-buffer +C-x d dired +C-x e call-last-kbd-macro +C-x f set-fill-column +C-x i insert-file +C-x k kill-buffer +C-x o other-window +C-x s save-some-buffers +C-x 4 C-f find-file-other-window +C-x 4 C-g keyboard-quit +C-x 4 b switch-to-buffer-other-window +C-x 4 f find-file-other-window + +M-C-g keyboard-quit +M-C-v scroll-other-window +M-SPC just-one-space +M-% query-replace +M-- negative-argument +M-0 digit-argument +M-1 digit-argument +M-2 digit-argument +M-3 digit-argument +M-4 digit-argument +M-5 digit-argument +M-6 digit-argument +M-7 digit-argument +M-8 digit-argument +M-9 digit-argument +M-< beginning-of-buffer +M-> end-of-buffer +M-[ backward-paragraph +M-\ delete-horizontal-space +M-] forward-paragraph +M-b backward-word +M-c capitalize-word +M-d kill-word +M-f forward-word +M-l downcase-word +M-q fill-paragraph +M-r search-backward +M-s search-forward +M-u upcase-word +M-v scroll-down +M-w copy-region-as-kill +M-x execute-extended-command +M-~ not-modified +M-DEL backward-kill-word + +\end{verbatim} + +\begin{theindex} +\addcontentsline{toc}{chapter}{Index} +\input{mgidx.tex} +\end{theindex} + +\end{document} diff --git a/usr.bin/mg/mgidx.tex b/usr.bin/mg/mgidx.tex new file mode 100644 index 00000000000..aa005a0cb59 --- /dev/null +++ b/usr.bin/mg/mgidx.tex @@ -0,0 +1,131 @@ +\indexentry{apropos}{31} +\indexentry{auto-fill-mode}{27} +\indexentry{auto-indent-mode}{28} +\indexentry{backward-char}{9} +\indexentry{backward-kill-word}{14} +\indexentry{backward-paragraph}{9} +\indexentry{backward-word}{9} +\indexentry{beginning-of-buffer}{9} +\indexentry{beginning-of-line}{9} +\indexentry{blink-matching-paren}{28} +\indexentry{blink-matching-paren-hack}{28} +\indexentry{call-last-kbd-macro}{32} +\indexentry{capitalize-word}{32} +\indexentry{cd}{25} +\indexentry{copy-region-as-kill}{14} +\indexentry{count-matches}{19} +\indexentry{count-non-matches}{19} +\indexentry{define-key}{34} +\indexentry{delete-backward-char}{14} +\indexentry{delete-blank-lines}{14} +\indexentry{delete-char}{14} +\indexentry{delete-horizontal-space}{15} +\indexentry{delete-matching-lines}{19} +\indexentry{delete-non-matching-lines}{19} +\indexentry{delete-other-windows}{21} +\indexentry{delete-window}{21} +\indexentry{describe-bindings}{31} +\indexentry{describe-key-briefly}{31} +\indexentry{digit-argument}{8} +\indexentry{dired}{29} +\indexentry{dired-backup-unflag}{29} +\indexentry{dired-copy-file}{29} +\indexentry{dired-do-deletions}{29} +\indexentry{dired-find-file}{29} +\indexentry{dired-find-file-other-window}{29} +\indexentry{dired-flag-file-deleted}{29} +\indexentry{dired-other-window}{30} +\indexentry{dired-rename-file}{30} +\indexentry{dired-unflag}{30} +\indexentry{downcase-region}{32} +\indexentry{downcase-word}{32} +\indexentry{emacs-version}{32} +\indexentry{end-kbd-macro}{32} +\indexentry{end-of-buffer}{9} +\indexentry{end-of-line}{10} +\indexentry{enlarge-window}{21} +\indexentry{eval-current-buffer}{36} +\indexentry{eval-expression}{36} +\indexentry{exchange-point-and-mark}{10} +\indexentry{execute-extended-command}{8} +\indexentry{fill-paragraph}{27} +\indexentry{find-file}{24} +\indexentry{find-file-other-window}{24} +\indexentry{forward-char}{10} +\indexentry{forward-paragraph}{10} +\indexentry{forward-word}{10} +\indexentry{global-set-key}{34} +\indexentry{global-unset-key}{34} +\indexentry{goto-line}{10} +\indexentry{help-help}{31} +\indexentry{insert}{12} +\indexentry{insert-buffer}{23} +\indexentry{insert-file}{24} +\indexentry{insert-with-wrap}{27} +\indexentry{isearch-backward}{16} +\indexentry{isearch-forward}{16} +\indexentry{just-one-space}{15} +\indexentry{keyboard-quit}{8} +\indexentry{kill-buffer}{23} +\indexentry{kill-line}{15} +\indexentry{kill-paragraph}{15} +\indexentry{kill-region}{15} +\indexentry{kill-word}{15} +\indexentry{list-buffers}{23} +\indexentry{load}{36} +\indexentry{local-set-key}{34} +\indexentry{local-unset-key}{35} +\indexentry{make-backup-files}{25} +\indexentry{meta-key-mode}{32} +\indexentry{negative-argument}{8} +\indexentry{newline}{12} +\indexentry{newline-and-indent}{12} +\indexentry{next-line}{10} +\indexentry{no-tab-mode}{26} +\indexentry{not-modified}{24} +\indexentry{open-line}{12} +\indexentry{other-window}{21} +\indexentry{overwrite-mode}{27} +\indexentry{prefix-region}{33} +\indexentry{previous-line}{10} +\indexentry{previous-window}{22} +\indexentry{pwd}{25} +\indexentry{query-replace}{17} +\indexentry{query-replace-regexp}{19} +\indexentry{quoted-insert}{12} +\indexentry{re-search-again}{20} +\indexentry{re-search-backward}{20} +\indexentry{re-search-forward}{20} +\indexentry{recenter}{10} +\indexentry{redraw-display}{11} +\indexentry{save-buffer}{24} +\indexentry{save-buffers-kill-emacs}{24} +\indexentry{save-some-buffers}{25} +\indexentry{scroll-down}{11} +\indexentry{scroll-one-line-down}{11} +\indexentry{scroll-one-line-up}{11} +\indexentry{scroll-other-window}{11} +\indexentry{scroll-up}{11} +\indexentry{search-again}{17} +\indexentry{search-backward}{17} +\indexentry{search-forward}{17} +\indexentry{self-insert-command}{13} +\indexentry{set-case-fold-search}{20} +\indexentry{set-default-mode}{26} +\indexentry{set-fill-column}{27} +\indexentry{set-mark-command}{11} +\indexentry{set-prefix-string}{33} +\indexentry{shrink-window}{22} +\indexentry{space-to-tabstop}{26} +\indexentry{split-window-vertically}{22} +\indexentry{start-kbd-macro}{32} +\indexentry{suspend-emacs}{33} +\indexentry{switch-to-buffer}{24} +\indexentry{switch-to-buffer-other-window}{24} +\indexentry{transpose-chars}{33} +\indexentry{universal-argument}{8} +\indexentry{upcase-region}{32} +\indexentry{upcase-word}{32} +\indexentry{what-cursor-position}{11} +\indexentry{write-file}{25} +\indexentry{yank}{15} diff --git a/usr.bin/mg/mgprog.doc b/usr.bin/mg/mgprog.doc new file mode 100644 index 00000000000..1fc9f8e2703 --- /dev/null +++ b/usr.bin/mg/mgprog.doc @@ -0,0 +1,295 @@ +This documentation covers mg 2a. + +I do want feedback from other mg developers on what they think of my +changes, documentation, and what needs to be done to make mg better. +This document is not complete, it mainly covers the areas I have +recently changed. + +Possible future changes: + +Rearange file contents along more rational lines. Further split the +monolithic def.h file. + +Changing the echo line stuff to use a minibuffer keymap. + +Making the kill buffer a linked list of lines. + +Variables. + +Allow for backspace, ^s, etc. to be changed in some reasonable manner. +(Probably using variables or a simulation thereof.) I do not think an +input keymap is the correct solution, even if it is frequently used in +Gnu emacs. (Besides the extra overhead, keynames come out wrong.) + +Make long lines wrap like they do in GNU emacs. + +Fix known (and unknown :-) bugs. + +Have the keymaps and associated tables generated by a program. + + +Known bugs/limitations: + +Binding a key in a named keymap may or may not change the binding of +other keys pointing to the same keymap. (i.e. if ^H and ^_ are bound +to help, rebinding ^Hb may not (or may) change ^_b. This can be cured +by rebinding ^_ to help.) + +Overwrite mode does not work in macros. (Characters are inserted +rather than overwriting.) + +Dired mode has some problems: Rename does not update the buffer. +Doing a dired again will update the buffer (whether it needs it or +not) and will lose any marks for deletion. .. and . are not +recognized as special cases. + + + +New implementation oddities: + +insert and define-key are new commands corresponding to the mocklisp +functions in Gnu Emacs. (Mg does not have non-command functions.) +(Mg's insert will only insert one string.) + +The display wrap code does not work at all like that of GNU emacs. + + + +Adding command functions to mg: + +Command functions take two integer aguments and return an integer. +The first argument, f, is a set of flags. (f&FFARG) is non-zero if a +numeric arguement was passed to the function. (There are bits +indicating how the agument was specified, but they are not fully +impleminted.) (f&FFRAND) is non-zero if the function is being called +by another function and that possibly slightly different action should +be taken. (No error checking, supress output, etc.) The second +argument, n, is the numeric agument passed or one if there was no +numeric arugment. The fuction should return TRUE if it executes +correctly, FALSE if it could not, and ABORT if the user typed the +keyboard quit character at a prompt. + +The function must be added to the functnames table in keymap.c. This +table must be kept in assending ascii sequence. + + + +Key maps: + +Key maps are structures containing information on what action should +be taken corresponding to an individual keypress. That action could +be an indication that this is a prefix key and the next kepress should +be looked up in another keymap. + + Example keymap: + + static struct KEYMAPE(6+IMAPEXT) cXmap = { + 6, + 6+IMAPEXT, + rescan, + { + {CCHR('B'),CCHR('G'), cXcB, (KEYMAP *)NULL}, + {CCHR('L'),CCHR('X'), cXcL, (KEYMAP *)NULL}, + {'(', ')', cXlp, (KEYMAP *)NULL}, + {'0', '4', cX0, (KEYMAP *)&cX4map}, + {'=', '=', cXeq, (KEYMAP *)NULL}, + {'^', 's', cXcar, (KEYMAP *)NULL}, + } + }; + +(Note: this example is a simplified example of a real keymap in keymap.c.) + +Since C does not directly support structures containing undementioned +arrays, the macro KEYMAPE is used to create a structure with an array +of the proper size. 6 is the current size of the array, and IMAPEXT +is the number of extra elements left for future groth (by rebinding +keys) before the map must be reallocated. rescan is the function to +be executed if a specific entry for the key is not found. (rescan is +a special function that searches for something else to do -- first by +trying lowercasing the last character in the keymap, then by trying +the other modes in effect.) + +The array elements must be in order by the keys they define. Each +covers a range of characters. Numeric values should not be used for +characters, they make porting mg to some other systems harder. The +CCHR macro may be used to specify control characters, including DEL +(CCHR('?')). cXcB, cXcL, etc. are arrays of pointers to functions +returning int. One of these fuction pointers per element may be to +the pseuto-function prefix. cX4map is the keymap coresponding to the +prefix function bound to '4' in this keymap. Having several keys in +an element bound to the default function is better than increasing the +number of elements. + + + +Modes: + +Modes are named key maps that are scanned for key bindings before the +global keymap is. There are functions in modes.c to toggle modes on +or off for individual buffers. Note that the "major"/"minor" mode +distiction is different than in Gnu Emacs. Dired is currently the +only major mode available, buffers are put into dired mode on creation +by the dired code. (The distiction in mg is the major mode replaces +the default keymap instead of being an overlay.) Some modes (overwrite +and no-tab) also trigger per-buffer flags that should be convered to +per-buffer variables when we add variables. Keymaps for the modes are +kept in keymap.c with the other keymaps. + + + +System dependent files: + +Fileio.c: + Contains file i/o routines: + ffputbuf(BUFFER *): +Write all lines of buffer to the file. The last line of the buffer does +NOT have the normally implied newline. (One may be added if +nessisary, but don't write out the last line if it is zero characters +long.) + + ffgetline(char *buf, int nbuf, int *nbytes): +Read a line from the file up to the length specified by the second +argument in to the buffer pointed to by the first. If a newline is +not found after reading nbuf characters, return FIOLONG and on the +next call to ffgetline return the remaining portion (or nbuf more +characters and return FIOLONG). If a newline is found, set *nbytes to +the number of characters read and return FIOSUC. If the end of file +is descovered, set *nbytes and return FIOEOF. (If the file ends in a +newline, the call returning FIOEOF will set *nbytes to 0.) If any +other error occurs, return FIOERR. + + char *adjustname(char *fname) +Standardize filename. On mono-case systems, lowercase the file name. +If NO_DIR is not defined, make fully qualified. (not relitive to the +current directory, which may change.) + + fncmp(char *fna, char *fnb) +Return 0 if both arguments refer to the same file or buffer. #define +it to be strcmp on mono-case or case sensitive systems, use a non-case +sensitive compareison otherwise. (Borrow it from the osk fileio.c) +Both arguments have been through adjustname already. + + routines needed for dired: + + rename(char *fromname, char *toname) +rename file. BSD systems have this as a system call. return -1 on +error. + + copy(char *fromname, char *toname) +copy file. return -1 on error. + + unlinkdir(char *name) +Delete directory. return -1 on error. (possibly including non-empty +directory.) + + BUFFER *dired_(char *name) +Create dired buffer for named directory. See example from osk or bsd. + + d_makename(LINE *l, char *fname) +Concatinate directory name associated with the current dired BUFFER +with file name in line. Return ABORT if no file name on line, FALSE +if it is an ordinary file, or TRUE if there is a directory. Name made +should be the same as if it were run through adjustname. + + +Converting system & terminal dependent files from mg1b: + +All command functions will have to be rewritten to use the new two +argument calling sequence. (Some compilers will let you get away +without doing this for testing purposes. Problems, if any, will show +up at run-time.) + +Key binding is completly different. See extend.c if your code needs +to do rebinding. + +I have attempted make mg less dependant on ascii character values. +Keymap.c depends on ascii sorting order. Cinfo.c depends on character +values and contains a function to convert from a character value +to a key name. Several modules assume 3 octal digits are enough for +any character value. + +Names of most compile time options have changed. Whatever is most +GNU-emacs like is now the default. + +sysdef.h: + The type KEY should not be defined. The type KCHAR should be defined. + All posible key inputs must be positive in type KCHAR. short is + recomended. + +spawn.c: + Update to the new function calling conventions. + +Makefile: + Needs complete rewrite. You can probably figure out the dependencies + from the bsd or osk one. Compile time options have changed. + +Fileio.c: + Remove function ffputline. Add function ffputbuf (described above). + Rewrite ffgetline to conform to the new way of doing things. (see + above.) These routines can probably be borrowed intact from the + OSK system dependant fileio.c for unix systems. + Replace adjustcase with adjustname. Add fncmp either here or + as a #define in sysdef.h. Add functions needed by dired. + + + + +Compile time options: + +extentions not directly in gnu emacs + +BSMAP input mapping exchanging ^H and DEL. + 1 for defaulting to this, 0 for normal default. +NOTAB for systems that don't like tabs +CVMVAS arguments to ^V in screens not lines +PREFIXREGION prefix region +PREVWIND previous window +GOSREC Gossling style recenter +STARTUPFILE (unix & OSK) system-wide startup file +XKEYS (Termcap) Put kepad in alternate mode, use + terminal-dependent startup file. + +Features removeable to save space + +NO_HELP help, descibe-bindings, describe-key-briefly, apropos +NO_MACRO keyboard macros. If defined, NO_STARTUP must be also. +NO_STARTUP startup files, load, etc. +NO_BACKUP backup files when writing +NO_DPROMPT Delayed prompt on multi-key sequences +NO_DIR Dir change functions. If defined, NO_DIRED must be also. +NO_DIRED Dired mode +REGEX Regular expressions. Not default, since the code is rather + unportable and has the GNU copywrite on it. + +System dependant garbage (avoid where practical) + +VMS VMS +AMIGA AMIGA + + +Things that may be defined in system dependant code: + +XCHAR XCHAR and XSHORT (char and short for space savings) +BDC2 more special characters for filenames +BDC3 dito. +METABIT Bit of KCHAR set on meta keys +OFFSET macro to calculate offset of member from start of structure +NBLOCK line growth amount +KBLOCK kill buffer growth amount +MALLOCROUND macro to predict malloc allocations stratagy +SYSINIT system dependant initialization +NO_VOID_TYPE compiler dosen't have type void +ZEROARRAY zero length arrays are allowed +BINDKEY include bindkey routine for use by system & terminal + dependent code. + +Terminal dependant + +DO_METAKEY meta key +METABIT Which bit in a KCHAR is used by the meta key (default 0x80) +STANDOUT_GLITCH standout (may) take character position(s) +GOSLING optimize redisplay +MEMMAP memory mapped display +MOVE_STANDOUT cursor addressing may be done in standout mode +FKEYS function keys do not fit in type char. + Not for use where function keys send multiple characters. diff --git a/usr.bin/mg/modes.c b/usr.bin/mg/modes.c new file mode 100644 index 00000000000..49144f2fd6f --- /dev/null +++ b/usr.bin/mg/modes.c @@ -0,0 +1,138 @@ +/* + * Commands to toggle modes. Without an argument, toggle mode. + * Negitive or zero argument, mode off. Positive argument, mode on. + */ + +#include "def.h" +#include "kbd.h" + +int defb_nmodes = 0; +MAPS *defb_modes[PBMODES] = {&map_table[0]}; +int defb_flag = 0; + +static int changemode(f, n, mode) +int f, n; +char *mode; +{ + register int i; + MAPS *m; + VOID upmodes(); + + 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) { + n = 0; /* mode already set */ + break; + } + } + if(n > 0) { + for(i=0; i <= curbp->b_nmodes; i++) + if(curbp->b_modes[i] == m) return TRUE; /* mode already set */ + 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; +} + +indentmode(f, n) +{ + return changemode(f, n, "indent"); +} + +fillmode(f, n) +{ + return changemode(f, n, "fill"); +} + +/* + * Fake the GNU "blink-matching-paren" variable. + */ +blinkparen(f, n) +{ + return changemode(f, n, "blink"); +} + +#ifdef NOTAB +notabmode(f, 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 + +overwrite(f, n) +int f, 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; +} + +set_default_mode(f, n) +int f, n; +{ + register int i; + register MAPS *m; + char mode[32]; + + if(eread("Set Default Mode: ", mode, 32, EFNEW) != TRUE) + return ABORT; + if((m = name_mode(mode)) == NULL) { + ewprintf("can't find mode %s", mode); + return FALSE; + } + if(!(f & FFARG)) { + for(i=0; i <= defb_nmodes; i++) + if(defb_modes[i] == m) { + n = 0; /* mode already set */ + break; + } + } + if(n > 0) { + for(i=0; i <= defb_nmodes; i++) + if(defb_modes[i] == m) return TRUE; /* mode already set */ + 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) return TRUE; /* mode wasn't set */ + for(; i < defb_nmodes; i++) + defb_modes[i] = defb_modes[i+1]; + defb_nmodes--; + } + if(strcmp(mode, "overwrite")==0) + if(n<=0) defb_flag &= ~BFOVERWRITE; + else defb_flag |= BFOVERWRITE; +#ifdef NOTAB + if(strcmp(mode, "notab")==0) + if(n<=0) defb_flag &= ~BFNOTAB; + else defb_flag |= BFNOTAB; +#endif + return TRUE; +} diff --git a/usr.bin/mg/paragraph.c b/usr.bin/mg/paragraph.c new file mode 100644 index 00000000000..b09aa7810f3 --- /dev/null +++ b/usr.bin/mg/paragraph.c @@ -0,0 +1,284 @@ +/* + * 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 + +/* + * go back to the begining of the current paragraph + * here we look for a <NL><NL> or <NL><TAB> or <NL><SPACE> + * combination to delimit the begining of a paragraph + */ +/*ARGSUSED*/ +gotobop(f, n) +{ + if (n < 0) /* the other way...*/ + return gotoeop(f, -n); + + while (n-- > 0) { /* for each one asked for */ + + /* first scan back until we are in a word */ + + while (backchar(FFRAND, 1) && !inword()) {} + curwp->w_doto = 0; /* and go to the B-O-Line */ + + /* and scan back until we hit a <NL><SP> <NL><TAB> or <NL><NL> */ + while (lback(curwp->w_dotp) != curbp->b_linep) + 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_linep) { + /* beond end of buffer, cleanup time */ + curwp->w_dotp = lback(curwp->w_dotp); + curwp->w_doto = llength(curwp->w_dotp); + } + } + break; + } + } + curwp->w_flag |= WFMOVE; /* force screen update */ + return TRUE; +} + +/* + * go forword to the end of the current paragraph + * here we look for a <NL><NL> or <NL><TAB> or <NL><SPACE> + * combination to delimit the begining of a paragraph + */ +/*ARGSUSED*/ +gotoeop(f, n) +{ + if (n < 0) /* the other way...*/ + return gotobop(f, -n); + + while (n-- > 0) { /* for each one asked for */ + + /* Find the first word on/after the current line */ + curwp->w_doto = 0; + while(forwchar(FFRAND, 1) && !inword()) {} + 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_linep) { + 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_linep) { + /* beond end of buffer, cleanup time */ + curwp->w_dotp = lback(curwp->w_dotp); + curwp->w_doto = llength(curwp->w_dotp); + break; + } + } + curwp->w_flag |= WFMOVE; /* force screen update */ + return TRUE; +} + +/* + * Fill the current paragraph according to the current + * fill column + */ +/*ARGSUSED*/ +fillpara(f, n) +{ + register int c; /* current char durring scan */ + register int wordlen; /* length of current word */ + register int clength; /* position on line during fill */ + register int i; /* index during word copy */ + register 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 */ + LINE *eopline; /* pointer to line just past EOP */ + char wbuf[MAXWORD]; /* buffer for current word */ + + /* 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() && 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) + return FALSE; + + /* if not a separator, just add it in */ + if (c != ' ' && c != '\t') { + if (wordlen < MAXWORD - 1) + wbuf[wordlen++] = c; + else { + /* You loose chars beyond MAXWORD if the word + * is to long. I'm to 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 tenatitive 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. + */ + if((eolflag || curwp->w_doto==llength(curwp->w_dotp) + || (c=lgetc(curwp->w_dotp,curwp->w_doto))==' ' + || c=='\t') + && ISEOSP(wbuf[wordlen-1]) + && 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 realy 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 + * begining of the blank line. */ + (VOID) backchar(FFRAND, 1); + return TRUE; +} + +/* delete n paragraphs starting with the current one */ +/*ARGSUSED*/ +killpara(f, n) +{ + register int status; /* returned status of functions */ + + while (n--) { /* for each paragraph to delete */ + + /* mark out the end and begining 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 begining of the paragraph */ + (VOID) gotobop(FFRAND, 1); + curwp->w_doto = 0; /* force us to the begining of line */ + + /* 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; +} + +/* + * check to see if we're past fillcol, and if so, + * justify this line. As a last step, justify the line. + */ +/*ARGSUSED*/ +fillword(f, n) +{ + register char c; + register 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_flag |= WFMOVE; + if (nce == 0 && curwp->w_doto != 0) return fillword(f, n); + return TRUE; +} + +/* Set fill column to n. */ +setfillcol(f, n) { + extern int getcolpos() ; + + fillcol = ((f & FFARG) ? n : getcolpos()); + ewprintf("Fill column set to %d", fillcol); + return TRUE; +} diff --git a/usr.bin/mg/random.c b/usr.bin/mg/random.c new file mode 100644 index 00000000000..297de45eba5 --- /dev/null +++ b/usr.bin/mg/random.c @@ -0,0 +1,444 @@ +/* + * Assorted commands. + * The 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" + +/* + * 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*/ +showcpos(f, n) +{ + register LINE *clp; + register long nchar; + long cchar; + register int nline, row; + int cline, cbyte; /* Current line/char/byte */ + int ratio; + + clp = lforw(curbp->b_linep); /* Collect the data. */ + nchar = 0; + nline = 0; + for (;;) { + ++nline; /* Count this line */ + if (clp == curwp->w_dotp) { + cline = nline; /* Mark line */ + cchar = nchar + curwp->w_doto; + if (curwp->w_doto == llength(clp)) + cbyte = '\n'; + else + cbyte = lgetc(clp, curwp->w_doto); + } + nchar += llength(clp); /* Now count the chars */ + clp = lforw(clp); + if (clp == curbp->b_linep) break; + nchar++; /* count the newline */ + } + row = curwp->w_toprow + 1; /* Determine row. */ + clp = curwp->w_linep; + while (clp!=curbp->b_linep && 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; +} + +getcolpos() { + register int col, i, c; + + col = 1; /* Determine column. */ + for (i=0; i<curwp->w_doto; ++i) { + c = lgetc(curwp->w_dotp, i); + if (c == '\t' +#ifdef NOTAB + && !(curbp->b_flag & BFNOTAB) +#endif + ) { + col |= 0x07; + ++col; + } else if (ISCTRL(c) != FALSE) + ++col; + ++col; + } + 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*/ +twiddle(f, n) +{ + register LINE *dotp; + register int doto; + register int cr; + VOID lchange(); + + dotp = curwp->w_dotp; + doto = curwp->w_doto; + if(doto==llength(dotp)) { + if(--doto<=0) return FALSE; + } else { + if(doto==0) return FALSE; + ++curwp->w_doto; + } + cr = lgetc(dotp, doto--); + lputc(dotp, doto+1, lgetc(dotp, doto)); + lputc(dotp, doto, cr); + 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 + * procerssors. They even handle the looping. Normally + * this is bound to "C-O". + */ +/*ARGSUSED*/ +openline(f, n) +{ + register int i; + register int s; + + if (n < 0) + return FALSE; + if (n == 0) + return TRUE; + i = n; /* Insert newlines. */ + do { + s = lnewline(); + } while (s==TRUE && --i); + if (s == TRUE) /* Then back up overtop */ + s = backchar(f | FFRAND, n); /* of them all. */ + return s; +} + +/* + * Insert a newline. + * [following "feature" not present in current version of + * Gnu, and now disabled here too] + * If you are at the end of the line and the + * next line is a blank line, just move into the + * blank line. This makes "C-O" and "C-X C-O" work + * nicely, and reduces the ammount of screen + * update that has to be done. This would not be + * as critical if screen update were a lot + * more efficient. + */ +/*ARGSUSED*/ +newline(f, n) +{ + register LINE *lp; + register int s; + + if (n < 0) return FALSE; + while (n--) { + lp = curwp->w_dotp; +#ifdef undef + if (llength(lp) == curwp->w_doto + && lforw(lp) != curbp->b_linep + && llength(lforw(lp)) == 0) { + if ((s=forwchar(FFRAND, 1)) != TRUE) + return s; + } else +#endif + 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*/ +deblank(f, n) +{ + register LINE *lp1; + register LINE *lp2; + register RSIZE nld; + + lp1 = curwp->w_dotp; + while (llength(lp1)==0 && (lp2=lback(lp1))!=curbp->b_linep) + lp1 = lp2; + lp2 = lp1; + nld = (RSIZE) 0; + while ((lp2=lforw(lp2))!=curbp->b_linep && 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. + */ +justone(f, n) { + (VOID) delwhite(f, n); + return linsert(1, ' '); +} +/* + * Delete any whitespace around dot. + */ +/*ARGSUSED*/ +delwhite(f, n) +{ + register int col, c, s; + + col = curwp->w_doto; + while (((c = lgetc(curwp->w_dotp, col)) == ' ' || c == '\t') + && col < llength(curwp->w_dotp)) + ++col; + do { + if (curwp->w_doto == 0) { + s = FALSE; + break; + } + if ((s = backchar(FFRAND, 1)) != TRUE) break; + } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) == ' ' || c == '\t'); + + if (s == TRUE) (VOID) forwchar(FFRAND, 1); + (VOID) ldelete((RSIZE)(col - curwp->w_doto), KNONE); + 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 subcomands failed. Normally bound + * to "C-J". + */ +/*ARGSUSED*/ +indent(f, n) +{ + register int nicol; + register int c; + register int i; + + if (n < 0) return (FALSE); + 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 + ((i=nicol/8)!=0 && linsert(i, '\t')==FALSE) || + ((i=nicol%8)!=0 && linsert(i, ' ')==FALSE)))) + return FALSE; + } + 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*/ +forwdel(f, n) +{ + if (n < 0) + return backdel(f | FFRAND, -n); + if (f & FFARG) { /* Really a kill. */ + 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*/ +backdel(f, n) +{ + register int s; + + if (n < 0) + return forwdel(f | FFRAND, -n); + if (f & FFARG) { /* Really a kill. */ + if ((lastflag&CFKILL) == 0) + kdelete(); + thisflag |= CFKILL; + } + if ((s=backchar(f | FFRAND, n)) == TRUE) + s = ldelete((RSIZE) n, (f & FFARG) ? KFORW : KNONE); + return s; +} + +/* + * 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*/ +killline(f, n) { + register RSIZE chunk; + register LINE *nextp; + register int i, c; + VOID kdelete(); + + if ((lastflag&CFKILL) == 0) /* Clear kill buffer if */ + kdelete(); /* last wasn't a kill. */ + 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+1; + nextp = lforw(curwp->w_dotp); + i = n; + while (--i) { + if (nextp == curbp->b_linep) + break; + chunk += llength(nextp)+1; + nextp = lforw(nextp); + } + } else { /* n <= 0 */ + chunk = curwp->w_doto; + curwp->w_doto = 0; + i = n; + while (i++) { + if (lback(curwp->w_dotp) == curbp->b_linep) + break; + curwp->w_dotp = lback(curwp->w_dotp); + curwp->w_flag |= WFMOVE; + chunk += llength(curwp->w_dotp)+1; + } + } + /* + * KFORW here is a bug. Should be KBACK/KFORW, but we need to + * rewrite the ldelete code (later)? + */ + return (ldelete(chunk, KFORW)); +} + +/* + * 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*/ +yank(f, n) +{ + register int c; + register int i; + register LINE *lp; + register int nline; + VOID isetmark(); + + if (n < 0) return FALSE; + nline = 0; /* Newline counting. */ + while (n--) { + isetmark(); /* mark around last yank */ + 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; + } + } + lp = curwp->w_linep; /* Cosmetic adjustment */ + if (curwp->w_dotp == lp) { /* if offscreen insert. */ + while (nline-- && lback(lp)!=curbp->b_linep) + lp = lback(lp); + curwp->w_linep = lp; /* Adjust framing. */ + curwp->w_flag |= WFHARD; + } + return TRUE; +} + +#ifdef NOTAB +/*ARGSUSED*/ +space_to_tabstop(f, n) +int f, n; +{ + if(n<0) return FALSE; + if(n==0) return TRUE; + return linsert((n<<3) - (curwp->w_doto & 7), ' '); +} +#endif diff --git a/usr.bin/mg/re_search.c b/usr.bin/mg/re_search.c new file mode 100644 index 00000000000..010cb6024e5 --- /dev/null +++ b/usr.bin/mg/re_search.c @@ -0,0 +1,706 @@ +/* + * regular expression search commands for + * MicroGnuEmacs + * + * This file contains functions to implement several of gnuemacs' + * regular expression functions for MicroGnuEmacs. Several of + * the routines below are just minor rearrangements of the MicroGnuEmacs + * non-regular expression search functions. Hence some of them date back + * in essential structure to the original MicroEMACS; others are modifications + * of Rich Ellison's code. I, Peter Newton, wrote about half from scratch. + * + * Although I have nothing to do with the GNU project, these functions + * require the GNU project's regular expression package (files regex.c and + * regex.h). Hence, this file comes under the same copyright notice + * as the GNU project's code. As far as I know, the rest of MicroGnuEmacs + * need not since it may be used independently of any GNU project code. In + * any case, I certainly do not warrant either the correctness or utility + * of this code. The GNU project copyright notice follows. Don't you + * wish they would make it a bit shorter! + */ + +/* +GNU Emacs copying permission notice Copyright (C) 1985 Richard M. Stallman + Verbatim copies of this document, including its copyright notice, + may be distributed by anyone in any manner. + Distribution with modifications is not permitted. + +GNU Emacs is distributed in the hope that it will be useful, +but without any warranty. No author or distributor +accepts responsibility to anyone for the consequences of using it +or for whether it serves any particular purpose or works at all, +unless he says so in writing. + +Everyone is granted permission to copy, modify and redistribute +GNU Emacs under the following conditions: + + Permission is granted to anyone to make or distribute verbatim copies + of GNU Emacs source code as received, in any medium, provided that all + copyright notices and permission and nonwarranty notices are preserved, + and that the distributor grants the recipient permission + for further redistribution as permitted by this document, + and gives him and points out to him an exact copy of this document + to inform him of his rights. + + Permission is granted to distribute modified versions + of GNU Emacs source code, or of portions of it, + under the above conditions, provided also that all + changed files carry prominent notices stating who last changed them + and that all the GNU-Emacs-derived material, including everything + packaged together with it and not independently usable, is + distributed under the conditions stated in this document. + + Permission is granted to distribute GNU Emacs in + compiled or executable form under the same conditions applying + for source code, provided that either + A. it is accompanied by the corresponding machine-readable + source code, or + B. it is accompanied by a written offer, with no time limit, + to give anyone a machine-readable copy of the corresponding + source code in return for reimbursement of the cost of distribution. + This written offer must permit verbatim duplication by anyone. + C. it is distributed by someone who received only the + executable form, and is accompanied by a copy of the + written offer of source code which he received along with it. + +In other words, you are welcome to use, share and improve GNU Emacs +You are forbidden to forbid anyone else to use, share and improve +what you give them. Help stamp out software-hoarding! +*/ + +#ifdef REGEX +#include "def.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) + +char re_pat[NPAT]; /* Regex pattern */ +int re_srch_lastdir = SRCH_NOPR; /* Last search flags. */ +int casefoldsearch = TRUE; /* Does search ignore case ? */ + +/* Indexed by a character, gives the upper case equivalent of the character */ + +static char upcase[0400] = + { 000, 001, 002, 003, 004, 005, 006, 007, + 010, 011, 012, 013, 014, 015, 016, 017, + 020, 021, 022, 023, 024, 025, 026, 027, + 030, 031, 032, 033, 034, 035, 036, 037, + 040, 041, 042, 043, 044, 045, 046, 047, + 050, 051, 052, 053, 054, 055, 056, 057, + 060, 061, 062, 063, 064, 065, 066, 067, + 070, 071, 072, 073, 074, 075, 076, 077, + 0100, 0101, 0102, 0103, 0104, 0105, 0106, 0107, + 0110, 0111, 0112, 0113, 0114, 0115, 0116, 0117, + 0120, 0121, 0122, 0123, 0124, 0125, 0126, 0127, + 0130, 0131, 0132, 0133, 0134, 0135, 0136, 0137, + 0140, 0101, 0102, 0103, 0104, 0105, 0106, 0107, + 0110, 0111, 0112, 0113, 0114, 0115, 0116, 0117, + 0120, 0121, 0122, 0123, 0124, 0125, 0126, 0127, + 0130, 0131, 0132, 0173, 0174, 0175, 0176, 0177, + 0200, 0201, 0202, 0203, 0204, 0205, 0206, 0207, + 0210, 0211, 0212, 0213, 0214, 0215, 0216, 0217, + 0220, 0221, 0222, 0223, 0224, 0225, 0226, 0227, + 0230, 0231, 0232, 0233, 0234, 0235, 0236, 0237, + 0240, 0241, 0242, 0243, 0244, 0245, 0246, 0247, + 0250, 0251, 0252, 0253, 0254, 0255, 0256, 0257, + 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, + 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, + 0300, 0301, 0302, 0303, 0304, 0305, 0306, 0307, + 0310, 0311, 0312, 0313, 0314, 0315, 0316, 0317, + 0320, 0321, 0322, 0323, 0324, 0325, 0326, 0327, + 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337, + 0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347, + 0350, 0351, 0352, 0353, 0354, 0355, 0356, 0357, + 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, + 0370, 0371, 0372, 0373, 0374, 0375, 0376, 0377 + }; + +/* + * Search forward. + * Get a search string from the user, and search for it, + * starting at ".". If found, "." gets moved to just after the + * matched characters, and display does all the hard stuff. + * If not found, it just prints a message. + */ +/*ARGSUSED*/ +re_forwsearch(f, n) { + register 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*/ +re_backsearch(f, n) { + register 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. + */ +/*ARGSUSED*/ +/* This code has problems-- some incompatibility(?) with + extend.c causes match to fail when it should not. + */ +re_searchagain(f, 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); + } +} + + +#include "regex.h" +#define BYTEWIDTH 8 + +/* Compiled regex goes here-- changed only when new pattern read */ +static struct re_pattern_buffer re_buff; +static char fastmap[(1 << BYTEWIDTH)]; + +/* regs holds boundaries of matched text */ +static struct re_registers regs; + +/* + * Re-Query Replace. + * Replace strings selectively. Does a search and replace operation. + */ +/*ARGSUSED*/ +re_queryrepl(f, n) { + register int s; + register int rcnt = 0; /* Replacements made so far */ + register int plen; /* length of found string */ + char news[NPAT]; /* replacement string */ + + /* Casefold check */ + if (!casefoldsearch) f = TRUE; + + if ((s=re_readpattern("RE Query replace")) != TRUE) + return (s); + if ((s=ereply("Query replace %s with: ",news, NPAT, re_pat)) == ABORT) + return (s); + if (s == FALSE) + news[0] = '\0'; + 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 = regs.end[0] - regs.start[0]; + if (re_doreplace((RSIZE) plen, news, f) == FALSE) + return (FALSE); + rcnt++; + break; + + case '.': + plen = regs.end[0] - regs.start[0]; + if (re_doreplace((RSIZE) plen, news, f) == FALSE) + return (FALSE); + rcnt++; + goto stopsearch; + + case CCHR('G'): /* ^G */ + (VOID) ctrlg(FFRAND, 0); + case CCHR('['): /* ESC */ + case '`': + goto stopsearch; + + case '!': + do { + plen = regs.end[0] - regs.start[0]; + if (re_doreplace((RSIZE) plen, news, f) == 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_flag |= WFHARD; + 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. + */ + +/* Maximum length of replacement string */ +#define REPLEN 256 + +re_doreplace(plen, st, f) + register RSIZE plen; /* length to remove */ + char *st; /* replacement string */ + int f; /* case hack disable */ +{ + int s; + int num, k; + register int j; + int more, state; + LINE *clp; + char repstr[REPLEN]; + + clp = curwp->w_dotp; + more = TRUE; + j = 0; + state = 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_NREGS) return(FALSE); + k = regs.end[num] - regs.start[num]; + if (j+k >= REPLEN) return(FALSE); + bcopy(&(clp->l_text[regs.start[num]]), &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; + } /* end case */ + } /* end while */ + + repstr[j] = '\0'; + + s = lreplace(plen, repstr, f); + + 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. + */ +re_forwsrch() { + + register LINE *clp; + register int tbo; + int ntries; + int i, plen; + + clp = curwp->w_dotp; + tbo = curwp->w_doto; + + if (tbo == clp->l_used) + /* Don't start matching off end of line-- must + * move to beginning of next line, unless at end + */ + if (clp != curbp->b_linep) { + 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_linep)) { + + ntries = llength(clp) - tbo; + i = re_search (&re_buff, ltext(clp), llength(clp), tbo, ntries, ®s); + + if (i == -1) { + clp = lforw(clp); + tbo = 0; + } + else { + curwp->w_doto = regs.end[0]; + curwp->w_dotp = clp; + curwp->w_flag |= 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. + */ +re_backsrch() { + + register LINE *clp; + register int tbo; + int ntries; + int i, startpos; +char m[1]; + + 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_linep)) { + + ntries = tbo; + i = re_search (&re_buff, ltext(clp), llength(clp), tbo, -ntries, ®s); + + if (i == -1) { + clp = lback(clp); + tbo = llength(clp); + } + else { + curwp->w_doto = regs.start[0]; + curwp->w_dotp = clp; + curwp->w_flag |= 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. + */ +re_readpattern(prompt) char *prompt; { + register int s; + char tpat[NPAT]; + char *message; + + if (re_pat[0] == '\0') s = ereply("%s: ", tpat, NPAT, prompt); + else s = ereply("%s: (default %s) ", tpat, NPAT, prompt, re_pat); + + if (s == TRUE) { + /* New pattern given */ + (VOID) strcpy(re_pat, tpat); + re_buff.allocated = 40; + re_buff.buffer = (char *) malloc (re_buff.allocated); + re_buff.fastmap = fastmap; + if (casefoldsearch) + re_buff.translate = upcase; + else + re_buff.translate = '\0'; + message = re_compile_pattern (re_pat, strlen(re_pat), &re_buff); + if (message != '\0') { + ewprintf("Regex Error: %s", message); + re_pat[0] = '\0'; + return(FALSE); + } + re_compile_fastmap (&re_buff); + } + else if (s==FALSE && re_pat[0]!='\0') + /* Just using old pattern */ + s = TRUE; + return (s); +} + + + +/* Cause case to not matter in searches. This is the default. If + * called with argument cause case to matter. + */ +setcasefold(f, 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); + +} /* end setcasefold */ + + +/* Delete all lines after dot that contain a string matching regex + */ +delmatchlines(f, 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 + */ +delnonmatchlines(f, 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 */ +killmatches(cond) + int cond; +{ + int s, i; + int count = 0; + LINE *clp; + + clp = curwp->w_dotp; + if (curwp->w_doto == llength(clp)) + /* Consider dot on next line */ + clp = lforw(clp); + + while (clp != (curbp->b_linep)) { + + /* see if line matches */ + i = re_search (&re_buff, ltext(clp), llength(clp), 0, llength(clp), + ®s); + /* Delete line when appropriate */ + if ((cond == FALSE && i == -1) || (cond == TRUE && i != -1)) { + curwp->w_doto = 0; + curwp->w_dotp = clp; + count++; + s = ldelete(llength(clp)+1, KNONE); + clp = curwp->w_dotp; + curwp->w_flag |= WFMOVE; + if (s == FALSE) return(FALSE); + } + else + clp = lforw(clp); + } + + ewprintf("%d line(s) deleted", count); + if (count > 0) curwp->w_flag |= WFMOVE; + + return(TRUE); +} + + +petersfunc(f, n) { + + int s; + LINE *clp; + char c; + + curwp->w_doto = 0; + s = ldelete(llength(curwp->w_dotp)+1, KNONE); + curwp->w_flag |= WFMOVE; + return(s); + +} + + +/* Count lines matching regex + */ +cntmatchlines(f, 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 + */ +cntnonmatchlines(f, 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 */ +countmatches(cond) + int cond; +{ + int s, i; + int count = 0; + LINE *clp; + + clp = curwp->w_dotp; + if (curwp->w_doto == llength(clp)) + /* Consider dot on next line */ + clp = lforw(clp); + + while (clp != (curbp->b_linep)) { + + /* see if line matches */ + i = re_search (&re_buff, ltext(clp), llength(clp), 0, llength(clp), + ®s); + /* Count line when appropriate */ + if ((cond == FALSE && i == -1) || (cond == TRUE && i != -1)) 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 diff --git a/usr.bin/mg/regex.c b/usr.bin/mg/regex.c new file mode 100644 index 00000000000..817e533ce8e --- /dev/null +++ b/usr.bin/mg/regex.c @@ -0,0 +1,1594 @@ +/* Imagine Generic gnuemacs copyright notice here */ + +/* modified by Robert Larson to make fewer assumptions about the compiler */ +/* (Making it useful to mg) (#if not supported by osk, enum not supported by many) */ + +/* To test, compile with -Dtest. + This Dtestable feature turns this into a self-contained program + which reads a pattern, describes how it compiles, + then reads a string and searches for it. */ + + +#ifdef REGEX +#include "def.h" /* defines VOID etc. for mg */ + +#ifdef emacs + +/* The `emacs' switch turns on certain special matching commands + that make sense only in emacs. */ + +#include "config.h" +#include "lisp.h" +#include "buffer.h" +#include "syntax.h" + +#else /* not emacs */ + +/* + * Define the syntax stuff, so we can do the \<...\> things. + */ + +#ifndef Sword /* must be non-zero in some of the tests below... */ +#define Sword 1 +#endif + +#define SYNTAX(c) re_syntax_table[c] + +#ifdef SYNTAX_TABLE + +char *re_syntax_table; + +#else + +static char re_syntax_table[256]; + +static VOID +init_syntax_once () +{ + register int c; + static int done = 0; + + if (done) + return; + + bzero (re_syntax_table, sizeof re_syntax_table); + + for (c = 'a'; c <= 'z'; c++) + re_syntax_table[c] = Sword; + + for (c = 'A'; c <= 'Z'; c++) + re_syntax_table[c] = Sword; + + for (c = '0'; c <= '9'; c++) + re_syntax_table[c] = Sword; + + done = 1; +} + +#endif /* SYNTAX_TABLE */ +#endif /* not emacs */ + +#include "regex.h" + +/* Number of failure points to allocate space for initially, + when matching. If this number is exceeded, more space is allocated, + so it is not a hard limit. */ + +#ifndef NFAILURES +#define NFAILURES 80 +#endif NFAILURES + +/* width of a byte in bits */ + +#define BYTEWIDTH 8 + +/* These are the command codes that appear in compiled regular expressions, one per byte. + Some command codes are followed by argument bytes. + A command code can specify any interpretation whatever for its arguments. + Zero-bytes may appear in the compiled regular expression. */ + +typedef char regexpcode; +#define unused 0 +#define exactn 1 /* followed by one byte giving n, and then by n literal bytes */ +#define begline 2 /* fails unless at beginning of line */ +#define endline 3 /* fails unless at end of line */ +#define jump 4 /* followed by two bytes giving relative address to jump to */ +#define on_failure_jump 5 /* followed by two bytes giving relative address of place */ + /* to resume at in case of failure. */ +#define finalize_jump 6 /* Throw away latest failure point and then jump to address. */ +#define maybe_finalize_jump 7 /* Like jump but finalize if safe to do so. */ + /* This is used to jump back to the beginning + of a repeat. If the command that follows + this jump is clearly incompatible with the + one at the beginning of the repeat, such that + we can be sure that there is no use backtracking + out of repetitions already completed, + then we finalize. */ +#define dummy_failure_jump 8 /* jump, and push a dummy failure point. */ + /* This failure point will be thrown away + if an attempt is made to use it for a failure. + A + construct makes this before the first repeat. */ +#define anychar 9 /* matches any one character */ +#define charset 10 /* matches any one char belonging to specified set. */ + /* First following byte is # bitmap bytes. + Then come bytes for a bit-map saying which chars are in. + Bits in each byte are ordered low-bit-first. + A character is in the set if its bit is 1. + A character too large to have a bit in the map + is automatically not in the set */ +#define charset_not 11 /* similar but match any character that is NOT one of those specified */ +#define start_memory 12 /* starts remembering the text that is matched */ + /* and stores it in a memory register. + followed by one byte containing the register number. + Register numbers must be in the range 0 through NREGS. */ +#define stop_memory 13 /* stops remembering the text that is matched */ + /* and stores it in a memory register. + followed by one byte containing the register number. + Register numbers must be in the range 0 through NREGS. */ +#define duplicate 14 /* match a duplicate of something remembered. */ + /* Followed by one byte containing the index of the memory register. */ +#define before_dot 15 /* Succeeds if before dot */ +#define at_dot 16 /* Succeeds if at dot */ +#define after_dot 17 /* Succeeds if after dot */ +#define begbuf 18 /* Succeeds if at beginning of buffer */ +#define endbuf 19 /* Succeeds if at end of buffer */ +#define wordchar 20 /* Matches any word-constituent character */ +#define notwordchar 21 /* Matches any char that is not a word-constituent */ +#define wordbeg 22 /* Succeeds if at word beginning */ +#define wordend 23 /* Succeeds if at word end */ +#define wordbound 24 /* Succeeds if at a word boundary */ +#define notwordbound 25 /* Succeeds if not at a word boundary */ +#define syntaxspec 26 /* Matches any character whose syntax is specified. */ + /* followed by a byte which contains a syntax code, Sword or such like */ +#define notsyntaxspec 27 /* Matches any character whose syntax differs from the specified. */ + + +#ifndef SIGN_EXTEND_CHAR +#define SIGN_EXTEND_CHAR(x) (x) +#endif + +/* compile_pattern takes a regular-expression descriptor string in the user's format + and converts it into a buffer full of byte commands for matching. + + pattern is the address of the pattern string + size is the length of it. + bufp is a struct re_pattern_buffer * which points to the info + on where to store the byte commands. + This structure contains a char * which points to the + actual space, which should have been obtained with malloc. + compile_pattern may use realloc to grow the buffer space. + + The number of bytes of commands can be found out by looking in + the struct re_pattern_buffer that bufp pointed to, + after compile_pattern returns. +*/ + +#define PATPUSH(ch) (*b++ = (char) (ch)) + +#define PATFETCH(c) \ + {if (p == pend) goto end_of_pattern; \ + c = * (unsigned char *) p++; \ + if (translate) c = translate[c]; } + +#define PATFETCH_RAW(c) \ + {if (p == pend) goto end_of_pattern; \ + c = * (unsigned char *) p++; } + +#define PATUNFETCH p-- + +#define EXTEND_BUFFER \ + { char *old_buffer = bufp->buffer; \ + if (bufp->allocated == (1<<16)) goto too_big; \ + bufp->allocated *= 2; \ + if (bufp->allocated > (1<<16)) bufp->allocated = (1<<16); \ + if (!(bufp->buffer = (char *) realloc (bufp->buffer, bufp->allocated))) \ + goto memory_exhausted; \ + c = bufp->buffer - old_buffer; \ + b += c; \ + if (fixup_jump) \ + fixup_jump += c; \ + if (laststart) \ + laststart += c; \ + begalt += c; \ + if (pending_exact) \ + pending_exact += c; \ + } + +static int store_jump (), insert_jump (); + +char * +re_compile_pattern (pattern, size, bufp) + char *pattern; + int size; + struct re_pattern_buffer *bufp; +{ + register char *b = bufp->buffer; + register char *p = pattern; + char *pend = pattern + size; + register unsigned c, c1; + char *p1; + unsigned char *translate = (unsigned char *) bufp->translate; + + /* address of the count-byte of the most recently inserted "exactn" command. + This makes it possible to tell whether a new exact-match character + can be added to that command or requires a new "exactn" command. */ + + char *pending_exact = 0; + + /* address of the place where a forward-jump should go + to the end of the containing expression. + Each alternative of an "or", except the last, ends with a forward-jump + of this sort. */ + + char *fixup_jump = 0; + + /* address of start of the most recently finished expression. + This tells postfix * where to find the start of its operand. */ + + char *laststart = 0; + + /* In processing a repeat, 1 means zero matches is allowed */ + + char zero_times_ok; + + /* In processing a repeat, 1 means many matches is allowed */ + + char many_times_ok; + + /* address of beginning of regexp, or inside of last \( */ + + char *begalt = b; + + /* Stack of information saved by \( and restored by \). + Four stack elements are pushed by each \(: + First, the value of b. + Second, the value of fixup_jump. + Third, the value of regnum. + Fourth, the value of begalt. */ + + int stackb[40]; + int *stackp = stackb; + int *stacke = stackb + 40; + int *stackt; + + /* Counts \('s as they are encountered. Remembered for the matching \), + where it becomes the "register number" to put in the stop_memory command */ + + int regnum = 1; + + bufp->fastmap_accurate = 0; + +#ifndef SYNTAX_TABLE +#ifndef emacs + /* + * Initialize the syntax table. + */ + init_syntax_once(); +#endif +#endif + + if (bufp->allocated == 0) + { + bufp->allocated = 28; + if (bufp->buffer) + /* EXTEND_BUFFER loses when bufp->allocated is 0 */ + bufp->buffer = (char *) realloc (bufp->buffer, 28); + else + /* Caller did not allocate a buffer. Do it for him */ + bufp->buffer = (char *) malloc (28); + if (!bufp->buffer) goto memory_exhausted; + begalt = b = bufp->buffer; + } + + while (p != pend) + { + if (b - bufp->buffer > bufp->allocated - 10) + /* Note that EXTEND_BUFFER clobbers c */ + EXTEND_BUFFER; + + PATFETCH (c); + + switch (c) + { + case '$': + /* $ means succeed if at end of line, but only in special contexts. + If randonly in the middle of a pattern, it is a normal character. */ + if (p == pend || (*p == '\\' && (p[1] == ')' || p[1] == '|'))) + { + PATPUSH (endline); + break; + } + goto normal_char; + + case '^': + /* ^ means succeed if at beg of line, but only if no preceding pattern. */ + if (laststart) goto normal_char; + PATPUSH (begline); + break; + + case '*': + case '+': + case '?': + /* If there is no previous pattern, char not special. */ + if (!laststart) + goto normal_char; + /* If there is a sequence of repetition chars, + collapse it down to equivalent to just one. */ + zero_times_ok = 0; + many_times_ok = 0; + while (1) + { + zero_times_ok |= c != '+'; + many_times_ok |= c != '?'; + if (p == pend) + break; + PATFETCH (c); + if (!(c == '*' || c == '+' || c == '?')) + { + PATUNFETCH; + break; + } + } + + /* Now we know whether 0 matches is allowed, + and whether 2 or more matches is allowed. */ + if (many_times_ok) + { + /* If more than one repetition is allowed, + put in a backward jump at the end. */ + store_jump (b, maybe_finalize_jump, laststart - 3); + b += 3; + } + insert_jump (on_failure_jump, laststart, b + 3, b); + pending_exact = 0; + b += 3; + if (!zero_times_ok) + { + /* At least one repetition required: insert before the loop + a skip over the initial on-failure-jump instruction */ + insert_jump (dummy_failure_jump, laststart, laststart + 6, b); + b += 3; + } + break; + + case '.': + laststart = b; + PATPUSH (anychar); + break; + + case '[': + if (b - bufp->buffer + > bufp->allocated - 3 - (1 << BYTEWIDTH) / BYTEWIDTH) + /* Note that EXTEND_BUFFER clobbers c */ + EXTEND_BUFFER; + + laststart = b; + if (*p == '^') + PATPUSH (charset_not), p++; + else + PATPUSH (charset); + p1 = p; + + PATPUSH ((1 << BYTEWIDTH) / BYTEWIDTH); + /* Clear the whole map */ + bzero (b, (1 << BYTEWIDTH) / BYTEWIDTH); + /* Read in characters and ranges, setting map bits */ + while (1) + { + PATFETCH (c); + if (c == ']' && p != p1 + 1) break; + if (*p == '-') + { + PATFETCH (c1); + PATFETCH (c1); + while (c <= c1) + b[c / BYTEWIDTH] |= 1 << (c % BYTEWIDTH), c++; + } + else + { + b[c / BYTEWIDTH] |= 1 << (c % BYTEWIDTH); + } + } + /* Discard any bitmap bytes that are all 0 at the end of the map. + Decrement the map-length byte too. */ + while (b[-1] > 0 && b[b[-1] - 1] == 0) + b[-1]--; + b += b[-1]; + break; + + case '\\': + if (p == pend) goto invalid_pattern; + PATFETCH_RAW (c); + switch (c) + { + case '(': + if (stackp == stacke) goto nesting_too_deep; + if (regnum < RE_NREGS) + { + PATPUSH (start_memory); + PATPUSH (regnum); + } + *stackp++ = b - bufp->buffer; + *stackp++ = fixup_jump ? fixup_jump - bufp->buffer + 1 : 0; + *stackp++ = regnum++; + *stackp++ = begalt - bufp->buffer; + fixup_jump = 0; + laststart = 0; + begalt = b; + break; + + case ')': + if (stackp == stackb) goto unmatched_close; + begalt = *--stackp + bufp->buffer; + if (fixup_jump) + store_jump (fixup_jump, jump, b); + if (stackp[-1] < RE_NREGS) + { + PATPUSH (stop_memory); + PATPUSH (stackp[-1]); + } + stackp -= 2; + fixup_jump = 0; + if (*stackp) + fixup_jump = *stackp + bufp->buffer - 1; + laststart = *--stackp + bufp->buffer; + break; + + case '|': + insert_jump (on_failure_jump, begalt, b + 6, b); + pending_exact = 0; + b += 3; + if (fixup_jump) + store_jump (fixup_jump, jump, b); + fixup_jump = b; + b += 3; + laststart = 0; + begalt = b; + break; + +#ifdef emacs + case '=': + PATPUSH (at_dot); + break; + + case 's': + laststart = b; + PATPUSH (syntaxspec); + PATFETCH (c); + PATPUSH (syntax_spec_code[c]); + break; + + case 'S': + laststart = b; + PATPUSH (notsyntaxspec); + PATFETCH (c); + PATPUSH (syntax_spec_code[c]); + break; +#endif emacs + + case 'w': + laststart = b; + PATPUSH (wordchar); + break; + + case 'W': + laststart = b; + PATPUSH (notwordchar); + break; + + case '<': + PATPUSH (wordbeg); + break; + + case '>': + PATPUSH (wordend); + break; + + case 'b': + PATPUSH (wordbound); + break; + + case 'B': + PATPUSH (notwordbound); + break; + + case '`': + PATPUSH (begbuf); + break; + + case '\'': + PATPUSH (endbuf); + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + c1 = c - '0'; + if (c1 >= regnum) + goto normal_char; + for (stackt = stackp - 2; stackt > stackb; stackt -= 4) + if (*stackt == c1) + goto normal_char; + laststart = b; + PATPUSH (duplicate); + PATPUSH (c1); + break; + default: + goto normal_char; + } + break; + + default: + normal_char: + if (!pending_exact || pending_exact + *pending_exact + 1 != b + || *pending_exact == 0177 || *p == '*' || *p == '^' + || *p == '+' || *p == '?') + { + laststart = b; + PATPUSH (exactn); + pending_exact = b; + PATPUSH (0); + } + PATPUSH (c); + (*pending_exact)++; + } + } + + if (fixup_jump) + store_jump (fixup_jump, jump, b); + + if (stackp != stackb) goto unmatched_open; + + bufp->used = b - bufp->buffer; + return 0; + + invalid_pattern: + return "Invalid regular expression"; + + unmatched_open: + return "Unmatched \\("; + + unmatched_close: + return "Unmatched \\)"; + + end_of_pattern: + return "Premature end of regular expression"; + + nesting_too_deep: + return "Nesting too deep"; + + too_big: + return "Regular expression too big"; + + memory_exhausted: + return "Memory exhausted"; +} + +/* Store where `from' points a jump operation to jump to where `to' points. + `opcode' is the opcode to store. */ + +static int +store_jump (from, opcode, to) + char *from, *to; + char opcode; +{ + from[0] = opcode; + from[1] = (to - (from + 3)) & 0377; + from[2] = (to - (from + 3)) >> 8; +} + +/* Open up space at char FROM, and insert there a jump to TO. + CURRENT_END gives te end of the storage no in use, + so we know how much data to copy up. + OP is the opcode of the jump to insert. + + If you call this function, you must zero out pending_exact. */ + +static int +insert_jump (op, from, to, current_end) + char op; + char *from, *to, *current_end; +{ + register char *pto = current_end + 3; + register char *pfrom = current_end; + while (pfrom != from) + *--pto = *--pfrom; + store_jump (from, op, to); +} + +/* Given a pattern, compute a fastmap from it. + The fastmap records which of the (1 << BYTEWIDTH) possible characters + can start a string that matches the pattern. + This fastmap is used by re_search to skip quickly over totally implausible text. + + The caller must supply the address of a (1 << BYTEWIDTH)-byte data area + as bufp->fastmap. + The other components of bufp describe the pattern to be used. */ + +VOID +re_compile_fastmap (bufp) + struct re_pattern_buffer *bufp; +{ + unsigned char *pattern = (unsigned char *) bufp->buffer; + int size = bufp->used; + register char *fastmap = bufp->fastmap; + register unsigned char *p = pattern; + register unsigned char *pend = pattern + size; + register int j, k; + unsigned char *translate = (unsigned char *) bufp->translate; + + unsigned char *stackb[NFAILURES]; + unsigned char **stackp = stackb; + + bzero (fastmap, (1 << BYTEWIDTH)); + bufp->fastmap_accurate = 1; + bufp->can_be_null = 0; + + while (p) + { + if (p == pend) + { + bufp->can_be_null = 1; + break; + } + switch ((regexpcode) *p++) + { + case exactn: + if (translate) + fastmap[translate[p[1]]] = 1; + else + fastmap[p[1]] = 1; + break; + + case begline: + case before_dot: + case at_dot: + case after_dot: + case begbuf: + case endbuf: + case wordbound: + case notwordbound: + case wordbeg: + case wordend: + continue; + + case endline: + if (translate) + fastmap[translate['\n']] = 1; + else + fastmap['\n'] = 1; + bufp->can_be_null = 1; + break; + + case finalize_jump: + case maybe_finalize_jump: + case jump: + case dummy_failure_jump: + bufp->can_be_null = 1; + j = *p++ & 0377; + j += SIGN_EXTEND_CHAR (*(char *)p++) << 8; + p += j; + if (j > 0) + continue; + /* Jump backward reached implies we just went through + the body of a loop and matched nothing. + Opcode jumped to should be an on_failure_jump. + Just treat it like an ordinary jump. + For a * loop, it has pushed its failure point already; + if so, discard that as redundant. */ + if ((regexpcode) *p != on_failure_jump) + continue; + p++; + j = *p++ & 0377; + j += SIGN_EXTEND_CHAR (*(char *)p++) << 8; + p += j; + if (stackp != stackb && *stackp == p) + stackp--; + continue; + + case on_failure_jump: + j = *p++ & 0377; + j += SIGN_EXTEND_CHAR (*(char *)p++) << 8; + *++stackp = p + j; + continue; + + case start_memory: + case stop_memory: + p++; + continue; + + case duplicate: + bufp->can_be_null = 1; + fastmap['\n'] = 1; + case anychar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (j != '\n') + fastmap[j] = 1; + if (bufp->can_be_null) + return; + /* Don't return; check the alternative paths + so we can set can_be_null if appropriate. */ + break; + + case wordchar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) == Sword) + fastmap[j] = 1; + break; + + case notwordchar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) != Sword) + fastmap[j] = 1; + break; + +#ifdef emacs + case syntaxspec: + k = *p++; + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) == (enum syntaxcode) k) + fastmap[j] = 1; + break; + + case notsyntaxspec: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) != (enum syntaxcode) k) + fastmap[j] = 1; + break; +#endif emacs + + case charset: + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) + if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))) + { + if (translate) + fastmap[translate[j]] = 1; + else + fastmap[j] = 1; + } + break; + + case charset_not: + /* Chars beyond end of map must be allowed */ + for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++) + if (translate) + fastmap[translate[j]] = 1; + else + fastmap[j] = 1; + + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) + if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))) + { + if (translate) + fastmap[translate[j]] = 1; + else + fastmap[j] = 1; + } + break; + } + + /* Get here means we have successfully found the possible starting characters + of one path of the pattern. We need not follow this path any farther. + Instead, look at the next alternative remembered in the stack. */ + if (stackp != stackb) + p = *stackp--; + else + break; + } +} + +/* Like re_search_2, below, but only one string is specified. */ + +int +re_search (pbufp, string, size, startpos, range, regs) + struct re_pattern_buffer *pbufp; + char *string; + int size, startpos, range; + struct re_registers *regs; +{ + return re_search_2 (pbufp, 0, 0, string, size, startpos, range, regs, size); +} + +/* Like re_match_2 but tries first a match starting at index `startpos', + then at startpos + 1, and so on. + `range' is the number of places to try before giving up. + If `range' is negative, the starting positions tried are + startpos, startpos - 1, etc. + It is up to the caller to make sure that range is not so large + as to take the starting position outside of the input strings. + +The value returned is the position at which the match was found, + or -1 if no match was found. */ + +int +re_search_2 (pbufp, string1, size1, string2, size2, startpos, range, regs, mstop) + struct re_pattern_buffer *pbufp; + char *string1, *string2; + int size1, size2; + int startpos; + register int range; + struct re_registers *regs; + int mstop; +{ + register char *fastmap = pbufp->fastmap; + register char *translate = pbufp->translate; + int total = size1 + size2; + + /* Update the fastmap now if not correct already */ + if (fastmap && !pbufp->fastmap_accurate) + re_compile_fastmap (pbufp); + + while (1) + { + /* If a fastmap is supplied, skip quickly over characters + that cannot possibly be the start of a match. + Note, however, that if the pattern can possibly match + the null string, we must test it at each starting point + so that we take the first null string we get. */ + + if (fastmap && startpos < total && !pbufp->can_be_null) + { + if (range > 0) + { + register int lim = 0; + register char *p; + int irange = range; + if (startpos < size1 && startpos + range >= size1) + lim = range - (size1 - startpos); + + p = &(startpos >= size1 ? string2 - size1 : string1)[startpos]; + + if (translate) + { + while (range > lim && !fastmap[translate[*p++]]) + range--; + } + else + { + while (range > lim && !fastmap[*p++]) + range--; + } + startpos += irange - range; + } + else + { + register char c; + if (startpos >= size1) c = string2[startpos - size1]; + else c = string1[startpos]; + if (translate ? !fastmap[translate[c]] : !fastmap[c]) + goto advance; + } + } + + if (range >= 0 && startpos == total + && fastmap && !pbufp->can_be_null) + return -1; + + if (0 <= re_match_2 (pbufp, string1, size1, string2, size2, startpos, regs, mstop)) + return startpos; + +#ifdef C_ALLOCA + alloca (0); +#endif /* C_ALLOCA */ + + advance: + if (!range) break; + if (range > 0) range--, startpos++; else range++, startpos--; + } + return -1; +} + +#ifndef emacs /* emacs never uses this */ +int +re_match (pbufp, string, size, pos, regs) + struct re_pattern_buffer *pbufp; + char *string; + int size, pos; + struct re_registers *regs; +{ + return re_match_2 (pbufp, 0, 0, string, size, pos, regs, size); +} +#endif /* emacs */ + +/* Match the pattern described by `pbufp' + against data which is the virtual concatenation of `string1' and `string2'. + `size1' and `size2' are the sizes of the two data strings. + Start the match at position `pos'. + Do not consider matching past the position `mstop'. + + If pbufp->fastmap is nonzero, then it had better be up to date. + + The reason that the data to match is specified as two components + which are to be regarded as concatenated + is so that this function can be used directly on the contents of an Emacs buffer. + + -1 is returned if there is no match. Otherwise the value is the length + of the substring which was matched. +*/ + +int +re_match_2 (pbufp, string1, size1, string2, size2, pos, regs, mstop) + struct re_pattern_buffer *pbufp; + char *string1, *string2; + int size1, size2; + int pos; + struct re_registers *regs; + int mstop; +{ + register char *p = pbufp->buffer; + register char *pend = p + pbufp->used; + /* End of first string */ + char *end1; + /* End of second string */ + char *end2; + /* Pointer just past last char to consider matching */ + char *end_match_1, *end_match_2; + register char *d, *dend; + register int mcnt; + char *translate = pbufp->translate; + + /* Failure point stack. Each place that can handle a failure further down the line + pushes a failure point on this stack. It consists of two char *'s. + The first one pushed is where to resume scanning the pattern; + the second pushed is where to resume scanning the strings. + If the latter is zero, the failure point is a "dummy". + If a failure happens and the innermost failure point is dormant, + it discards that failure point and tries the next one. */ + + char **stackb = (char **) alloca (2 * NFAILURES * sizeof (char *)); + char **stackp = stackb, **stacke = &stackb[2 * NFAILURES]; + + /* Information on the "contents" of registers. + These are pointers into the input strings; they record + just what was matched (on this attempt) by some part of the pattern. + The start_memory command stores the start of a register's contents + and the stop_memory command stores the end. + + At that point, regstart[regnum] points to the first character in the register, + regend[regnum] points to the first character beyond the end of the register, + and regstart_segend[regnum] is either the same as regend[regnum] + or else points to the end of the input string into which regstart[regnum] points. + The latter case happens when regstart[regnum] is in string1 and + regend[regnum] is in string2. */ + + char *regstart[RE_NREGS]; + char *regstart_segend[RE_NREGS]; + char *regend[RE_NREGS]; + + /* Set up pointers to ends of strings. + Don't allow the second string to be empty unless both are empty. */ + if (!size2) + { + string2 = string1; + size2 = size1; + string1 = 0; + size1 = 0; + } + end1 = string1 + size1; + end2 = string2 + size2; + + /* Compute where to stop matching, within the two strings */ + if (mstop <= size1) + { + end_match_1 = string1 + mstop; + end_match_2 = string2; + } + else + { + end_match_1 = end1; + end_match_2 = string2 + mstop - size1; + } + + /* Initialize \( and \) text positions to -1 + to mark ones that no \( or \) has been seen for. */ + + for (mcnt = 0; mcnt < sizeof (regstart) / sizeof (*regstart); mcnt++) + regstart[mcnt] = (char *) -1; + + /* `p' scans through the pattern as `d' scans through the data. + `dend' is the end of the input string that `d' points within. + `d' is advanced into the following input string whenever necessary, + but this happens before fetching; + therefore, at the beginning of the loop, + `d' can be pointing at the end of a string, + but it cannot equal string2. */ + + if (pos <= size1) + d = string1 + pos, dend = end_match_1; + else + d = string2 + pos - size1, dend = end_match_2; + +/* Write PREFETCH; just before fetching a character with *d. */ +#define PREFETCH \ + while (d == dend) \ + { if (dend == end_match_2) goto fail; /* end of string2 => failure */ \ + d = string2; /* end of string1 => advance to string2. */ \ + dend = end_match_2; } + + /* This loop loops over pattern commands. + It exits by returning from the function if match is complete, + or it drops through if match fails at this starting point in the input data. */ + + while (1) + { + if (p == pend) + /* End of pattern means we have succeeded! */ + { + /* If caller wants register contents data back, convert it to indices */ + if (regs) + { + regend[0] = d; + regstart[0] = string1; + for (mcnt = 0; mcnt < RE_NREGS; mcnt++) + { + if ((mcnt != 0) && regstart[mcnt] == (char *) -1) + { + regs->start[mcnt] = -1; + regs->end[mcnt] = -1; + continue; + } + if (regstart[mcnt] - string1 < 0 || + regstart[mcnt] - string1 > size1) + regs->start[mcnt] = regstart[mcnt] - string2 + size1; + else + regs->start[mcnt] = regstart[mcnt] - string1; + if (regend[mcnt] - string1 < 0 || + regend[mcnt] - string1 > size1) + regs->end[mcnt] = regend[mcnt] - string2 + size1; + else + regs->end[mcnt] = regend[mcnt] - string1; + } + regs->start[0] = pos; + } + if (d - string1 >= 0 && d - string1 <= size1) + return d - string1 - pos; + else + return d - string2 + size1 - pos; + } + + /* Otherwise match next pattern command */ + switch ((regexpcode) *p++) + { + + /* \( is represented by a start_memory, \) by a stop_memory. + Both of those commands contain a "register number" argument. + The text matched within the \( and \) is recorded under that number. + Then, \<digit> turns into a `duplicate' command which + is followed by the numeric value of <digit> as the register number. */ + + case start_memory: + regstart[*p] = d; + regstart_segend[*p++] = dend; + break; + + case stop_memory: + regend[*p] = d; + if (regstart_segend[*p] == dend) + regstart_segend[*p] = d; + p++; + break; + + case duplicate: + { + int regno = *p++; /* Get which register to match against */ + register char *d2, *dend2; + + d2 = regstart[regno]; + dend2 = regstart_segend[regno]; + while (1) + { + /* Advance to next segment in register contents, if necessary */ + while (d2 == dend2) + { + if (dend2 == end_match_2) break; + if (dend2 == regend[regno]) break; + d2 = string2, dend2 = regend[regno]; /* end of string1 => advance to string2. */ + } + /* At end of register contents => success */ + if (d2 == dend2) break; + + /* Advance to next segment in data being matched, if necessary */ + PREFETCH; + + /* mcnt gets # consecutive chars to compare */ + mcnt = dend - d; + if (mcnt > dend2 - d2) + mcnt = dend2 - d2; + /* Compare that many; failure if mismatch, else skip them. */ + if (translate ? bcmp_translate (d, d2, mcnt, translate) : bcmp (d, d2, mcnt)) + goto fail; + d += mcnt, d2 += mcnt; + } + } + break; + + case anychar: + /* fetch a data character */ + PREFETCH; + /* Match anything but a newline. */ + if ((translate ? translate[*d++] : *d++) == '\n') + goto fail; + break; + + case charset: + case charset_not: + { + /* Nonzero for charset_not */ + int not = 0; + register int c; + if (*(p - 1) == (char) charset_not) + not = 1; + + /* fetch a data character */ + PREFETCH; + + if (translate) + c = translate [*(unsigned char *)d]; + else + c = *(unsigned char *)d; + + if (c < *p * BYTEWIDTH + && p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + not = !not; + + p += 1 + *p; + + if (!not) goto fail; + d++; + break; + } + + case begline: + if (d == string1 || d[-1] == '\n') + break; + goto fail; + + case endline: + if (d == end2 + || (d == end1 ? (size2 == 0 || *string2 == '\n') : *d == '\n')) + break; + goto fail; + + /* "or" constructs ("|") are handled by starting each alternative + with an on_failure_jump that points to the start of the next alternative. + Each alternative except the last ends with a jump to the joining point. + (Actually, each jump except for the last one really jumps + to the following jump, because tensioning the jumps is a hassle.) */ + + /* The start of a stupid repeat has an on_failure_jump that points + past the end of the repeat text. + This makes a failure point so that, on failure to match a repetition, + matching restarts past as many repetitions have been found + with no way to fail and look for another one. */ + + /* A smart repeat is similar but loops back to the on_failure_jump + so that each repetition makes another failure point. */ + + case on_failure_jump: + if (stackp == stacke) + { + char **stackx = (char **) alloca (2 * (stacke - stackb) * sizeof (char *)); + bcopy (stackb, stackx, (stacke - stackb) * sizeof (char *)); + stackp += stackx - stackb; + stacke = stackx + 2 * (stacke - stackb); + stackb = stackx; + } + mcnt = *p++ & 0377; + mcnt += SIGN_EXTEND_CHAR (*p++) << 8; + *stackp++ = mcnt + p; + *stackp++ = d; + break; + + /* The end of a smart repeat has an maybe_finalize_jump back. + Change it either to a finalize_jump or an ordinary jump. */ + + case maybe_finalize_jump: + mcnt = *p++ & 0377; + mcnt += SIGN_EXTEND_CHAR (*p++) << 8; + /* Compare what follows with the begining of the repeat. + If we can establish that there is nothing that they would + both match, we can change to finalize_jump */ + if (p == pend) + p[-3] = (char) finalize_jump; + else if (*p == (char) exactn || *p == (char) endline) + { + register int c = *p == (char) endline ? '\n' : p[2]; + register char *p1 = p + mcnt; + /* p1[0] ... p1[2] are an on_failure_jump. + Examine what follows that */ + if (p1[3] == (char) exactn && p1[5] != c) + p[-3] = (char) finalize_jump; + else if (p1[3] == (char) charset || p1[3] == (char) charset_not) + { + int not = p1[3] == (char) charset_not; + if (c < p1[4] * BYTEWIDTH + && p1[5 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + not = !not; + /* not is 1 if c would match */ + /* That means it is not safe to finalize */ + if (!not) + p[-3] = (char) finalize_jump; + } + } + p -= 2; + if (p[-1] != (char) finalize_jump) + { + p[-1] = (char) jump; + goto nofinalize; + } + + /* The end of a stupid repeat has a finalize-jump + back to the start, where another failure point will be made + which will point after all the repetitions found so far. */ + + case finalize_jump: + stackp -= 2; + + case jump: + nofinalize: + mcnt = *p++ & 0377; + mcnt += SIGN_EXTEND_CHAR (*p++) << 8; + p += mcnt; + break; + + case dummy_failure_jump: + if (stackp == stacke) + { + char **stackx = (char **) alloca (2 * (stacke - stackb) * sizeof (char *)); + bcopy (stackb, stackx, (stacke - stackb) * sizeof (char *)); + stackp += stackx - stackb; + stacke = stackx + 2 * (stacke - stackb); + stackb = stackx; + } + *stackp++ = 0; + *stackp++ = 0; + goto nofinalize; + + case wordbound: + if (d == string1 /* Points to first char */ + || d == end2 /* Points to end */ + || (d == end1 && size2 == 0)) /* Points to end */ + break; + if ((SYNTAX (((unsigned char *)d)[-1]) == Sword) + != (SYNTAX (d == end1 ? *(unsigned char *)string2 : *(unsigned char *)d) == Sword)) + break; + goto fail; + + case notwordbound: + if (d == string1 /* Points to first char */ + || d == end2 /* Points to end */ + || (d == end1 && size2 == 0)) /* Points to end */ + goto fail; + if ((SYNTAX (((unsigned char *)d)[-1]) == Sword) + != (SYNTAX (d == end1 ? *(unsigned char *)string2 : *(unsigned char *)d) == Sword)) + goto fail; + break; + + case wordbeg: + if (d == end2 /* Points to end */ + || (d == end1 && size2 == 0) /* Points to end */ + || SYNTAX (*(unsigned char *) (d == end1 ? string2 : d)) != Sword) /* Next char not a letter */ + goto fail; + if (d == string1 /* Points to first char */ + || SYNTAX (((unsigned char *)d)[-1]) != Sword) /* prev char not letter */ + break; + goto fail; + + case wordend: + if (d == string1 /* Points to first char */ + || SYNTAX (((unsigned char *)d)[-1]) != Sword) /* prev char not letter */ + goto fail; + if (d == end2 /* Points to end */ + || (d == end1 && size2 == 0) /* Points to end */ + || SYNTAX (d == end1 ? *(unsigned char *)string2 : *(unsigned char *)d) != Sword) /* Next char not a letter */ + break; + goto fail; + +#ifdef emacs + case before_dot: + if (((d - string2 <= (unsigned) size2) + ? d - (char *) bf_p2 : d - (char *) bf_p1) + <= point) + goto fail; + break; + + case at_dot: + if (((d - string2 <= (unsigned) size2) + ? d - (char *) bf_p2 : d - (char *) bf_p1) + == point) + goto fail; + break; + + case after_dot: + if (((d - string2 <= (unsigned) size2) + ? d - (char *) bf_p2 : d - (char *) bf_p1) + >= point) + goto fail; + break; + + case wordchar: + mcnt = (int) Sword; + goto matchsyntax; + + case syntaxspec: + mcnt = *p++; + matchsyntax: + PREFETCH; + if (SYNTAX (*(unsigned char *)d++) != (enum syntaxcode) mcnt) goto fail; + break; + + case notwordchar: + mcnt = (int) Sword; + goto matchnotsyntax; + + case notsyntaxspec: + mcnt = *p++; + matchnotsyntax: + PREFETCH; + if (SYNTAX (*(unsigned char *)d++) == (enum syntaxcode) mcnt) goto fail; + break; +#else + case wordchar: + PREFETCH; + if (SYNTAX (*(unsigned char *)d++) == 0) goto fail; + break; + + case notwordchar: + PREFETCH; + if (SYNTAX (*(unsigned char *)d++) != 0) goto fail; + break; +#endif not emacs + + case begbuf: + if (d == string1) /* Note, d cannot equal string2 */ + break; /* unless string1 == string2. */ + goto fail; + + case endbuf: + if (d == end2 || (d == end1 && size2 == 0)) + break; + goto fail; + + case exactn: + /* Match the next few pattern characters exactly. + mcnt is how many characters to match. */ + mcnt = *p++; + if (translate) + { + do + { + PREFETCH; + if (translate[*(unsigned char *)d++] != *p++) goto fail; + } + while (--mcnt); + } + else + { + do + { + PREFETCH; + if (*d++ != *p++) goto fail; + } + while (--mcnt); + } + break; + } + continue; /* Successfully matched one pattern command; keep matching */ + + /* Jump here if any matching operation fails. */ + fail: + if (stackp != stackb) + /* A restart point is known. Restart there and pop it. */ + { + if (!stackp[-2]) + { /* If innermost failure point is dormant, flush it and keep looking */ + stackp -= 2; + goto fail; + } + d = *--stackp; + p = *--stackp; + if (d >= string1 && d <= end1) + dend = end_match_1; + } + else break; /* Matching at this starting point really fails! */ + } + return -1; /* Failure to match */ +} + +static int +bcmp_translate (s1, s2, len, translate) + char *s1, *s2; + register int len; + char *translate; +{ + register char *p1 = s1, *p2 = s2; + while (len) + { + if (translate [*p1++] != translate [*p2++]) return 1; + len--; + } + return 0; +} + +/* Entry points compatible with bsd4.2 regex library */ + +#ifndef emacs + +static struct re_pattern_buffer re_comp_buf; + +char * +re_comp (s) + char *s; +{ + if (!s) + { + if (!re_comp_buf.buffer) + return "No previous regular expression"; + return 0; + } + + if (!re_comp_buf.buffer) + { + if (!(re_comp_buf.buffer = (char *) malloc (200))) + return "Memory exhausted"; + re_comp_buf.allocated = 200; + if (!(re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH))) + return "Memory exhausted"; + } + return re_compile_pattern (s, strlen (s), &re_comp_buf); +} + +int +re_exec (s) + char *s; +{ + int len = strlen (s); + return 0 <= re_search (&re_comp_buf, s, len, 0, len, 0); +} + +#endif /* emacs */ + +#ifdef test + +#include <stdio.h> + +/* Indexed by a character, gives the upper case equivalent of the character */ + +static char upcase[0400] = + { 000, 001, 002, 003, 004, 005, 006, 007, + 010, 011, 012, 013, 014, 015, 016, 017, + 020, 021, 022, 023, 024, 025, 026, 027, + 030, 031, 032, 033, 034, 035, 036, 037, + 040, 041, 042, 043, 044, 045, 046, 047, + 050, 051, 052, 053, 054, 055, 056, 057, + 060, 061, 062, 063, 064, 065, 066, 067, + 070, 071, 072, 073, 074, 075, 076, 077, + 0100, 0101, 0102, 0103, 0104, 0105, 0106, 0107, + 0110, 0111, 0112, 0113, 0114, 0115, 0116, 0117, + 0120, 0121, 0122, 0123, 0124, 0125, 0126, 0127, + 0130, 0131, 0132, 0133, 0134, 0135, 0136, 0137, + 0140, 0101, 0102, 0103, 0104, 0105, 0106, 0107, + 0110, 0111, 0112, 0113, 0114, 0115, 0116, 0117, + 0120, 0121, 0122, 0123, 0124, 0125, 0126, 0127, + 0130, 0131, 0132, 0173, 0174, 0175, 0176, 0177, + 0200, 0201, 0202, 0203, 0204, 0205, 0206, 0207, + 0210, 0211, 0212, 0213, 0214, 0215, 0216, 0217, + 0220, 0221, 0222, 0223, 0224, 0225, 0226, 0227, + 0230, 0231, 0232, 0233, 0234, 0235, 0236, 0237, + 0240, 0241, 0242, 0243, 0244, 0245, 0246, 0247, + 0250, 0251, 0252, 0253, 0254, 0255, 0256, 0257, + 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, + 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, + 0300, 0301, 0302, 0303, 0304, 0305, 0306, 0307, + 0310, 0311, 0312, 0313, 0314, 0315, 0316, 0317, + 0320, 0321, 0322, 0323, 0324, 0325, 0326, 0327, + 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337, + 0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347, + 0350, 0351, 0352, 0353, 0354, 0355, 0356, 0357, + 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, + 0370, 0371, 0372, 0373, 0374, 0375, 0376, 0377 + }; + +main () +{ + char pat[80]; + struct re_pattern_buffer buf; + struct re_registers regs; + int i; + char c; + char fastmap[(1 << BYTEWIDTH)]; + + buf.allocated = 40; + buf.buffer = (char *) malloc (buf.allocated); + buf.fastmap = fastmap; + buf.translate = upcase; + + while (1) + { + printf("Enter pattern\n"); + gets (pat); + + if (*pat) + { + re_compile_pattern (pat, strlen(pat), &buf); + + for (i = 0; i < buf.used; i++) + printchar (buf.buffer[i]); + + putchar ('\n'); + + printf ("%d allocated, %d used.\n", buf.allocated, buf.used); + + re_compile_fastmap (&buf); + printf ("Allowed by fastmap: "); + for (i = 0; i < (1 << BYTEWIDTH); i++) + if (fastmap[i]) printchar (i); + putchar ('\n'); + } + + printf("enter string to search\n"); + gets (pat); /* Now read the string to match against */ + +/* i = re_match (&buf, pat, strlen (pat), 0, 0); */ + i = re_search (&buf, pat, strlen (pat), 0, strlen (pat), ®s); + printf ("Match value %d.\n", i); + for (i=0; i < RE_NREGS; i++) + printf("%2d start %2d end %2d\n", i, regs.start[i], regs.end[i]); + } +} + +#ifdef NOTDEF +print_buf (bufp) + struct re_pattern_buffer *bufp; +{ + int i; + + printf ("buf is :\n----------------\n"); + for (i = 0; i < bufp->used; i++) + printchar (bufp->buffer[i]); + + printf ("\n%d allocated, %d used.\n", bufp->allocated, bufp->used); + + printf ("Allowed by fastmap: "); + for (i = 0; i < (1 << BYTEWIDTH); i++) + if (bufp->fastmap[i]) + printchar (i); + printf ("\nAllowed by translate: "); + if (bufp->translate) + for (i = 0; i < (1 << BYTEWIDTH); i++) + if (bufp->translate[i]) + printchar (i); + printf ("\nfastmap is%s accurate\n", bufp->fastmap_accurate ? "" : "n't"); + printf ("can %s be null\n----------", bufp->can_be_null ? "" : "not"); +} +#endif + +printchar (c) + char c; +{ + if (c < 041 || c >= 0177) + { + putchar ('\\'); + putchar (((c >> 6) & 3) + '0'); + putchar (((c >> 3) & 7) + '0'); + putchar ((c & 7) + '0'); + } + else + putchar (c); +} + +error (string) + char *string; +{ + puts (string); + exit (1); +} + +#endif /* test */ +#endif /* REGEX */ diff --git a/usr.bin/mg/regex.h b/usr.bin/mg/regex.h new file mode 100644 index 00000000000..c0a37b7a9b4 --- /dev/null +++ b/usr.bin/mg/regex.h @@ -0,0 +1,106 @@ +/* Definitions for data structures callers pass the regex library. + Copyright (C) 1985 Richard M. Stallman + +This program is distributed in the hope that it will be useful, +but without any warranty. No author or distributor +accepts responsibility to anyone for the consequences of using it +or for whether it serves any particular purpose or works at all, +unless he says so in writing. + + Permission is granted to anyone to distribute verbatim copies + of this program's source code as received, in any medium, provided that + the copyright notice, the nonwarraty notice above + and this permission notice are preserved, + and that the distributor grants the recipient all rights + for further redistribution as permitted by this notice, + and informs him of these rights. + + Permission is granted to distribute modified versions of this + program's source code, or of portions of it, under the above + conditions, plus the conditions that all changed files carry + prominent notices stating who last changed them and that the + derived material, including anything packaged together with it and + conceptually functioning as a modification of it rather than an + application of it, is in its entirety subject to a permission + notice identical to this one. + + Permission is granted to distribute this program (verbatim or + as modified) in compiled or executable form, provided verbatim + redistribution is permitted as stated above for source code, and + A. it is accompanied by the corresponding machine-readable + source code, under the above conditions, or + B. it is accompanied by a written offer, with no time limit, + to distribute the corresponding machine-readable source code, + under the above conditions, to any one, in return for reimbursement + of the cost of distribution. Verbatim redistribution of the + written offer must be permitted. Or, + C. it is distributed by someone who received only the + compiled or executable form, and is accompanied by a copy of the + written offer of source code which he received along with it. + + Permission is granted to distribute this program (verbatim or as modified) + in executable form as part of a larger system provided that the source + code for this program, including any modifications used, + is also distributed or offered as stated in the preceding paragraph. + +In other words, you are welcome to use, share and improve this program. +You are forbidden to forbid anyone else to use, share and improve +what you give them. Help stamp out software-hoarding! */ + + +#ifndef RE_NREGS +#define RE_NREGS 10 +#endif + +/* This data structure is used to represent a compiled pattern. */ + +struct re_pattern_buffer + { + char *buffer; /* Space holding the compiled pattern commands. */ + int allocated; /* Size of space that buffer points to */ + int used; /* Length of portion of buffer actually occupied */ + char *fastmap; /* Pointer to fastmap, if any, or zero if none. */ + /* re_search uses the fastmap, if there is one, + to skip quickly over totally implausible characters */ + char *translate; /* Translate table to apply to all characters before comparing. + Or zero for no translation. + The translation is applied to a pattern when it is compiled + and to data when it is matched. */ + char fastmap_accurate; + /* Set to zero when a new pattern is stored, + set to one when the fastmap is updated from it. */ + char can_be_null; /* Set to one by compiling fastmap + if this pattern might match the null string. + It does not necessarily match the null string + in that case, but if this is zero, it cannot. */ + }; + +/* Structure to store "register" contents data in. + + Pass the address of such a structure as an argument to re_match, etc., + if you want this information back. + + start[i] and end[i] record the string matched by \( ... \) grouping i, + for i from 1 to RE_NREGS - 1. + start[0] and end[0] record the entire string matched. */ + +struct re_registers + { + int start[RE_NREGS]; + int end[RE_NREGS]; + }; + + +extern char *re_compile_pattern (); +/* Is this really advertised? */ +extern VOID re_compile_fastmap (); +extern int re_search (), re_search_2 (); +extern int re_match (), re_match_2 (); + +/* 4.2 bsd compatibility (yuck) */ +extern char *re_comp (); +extern int re_exec (); + +#ifdef SYNTAX_TABLE +extern char *re_syntax_table; +#endif diff --git a/usr.bin/mg/region.c b/usr.bin/mg/region.c new file mode 100644 index 00000000000..8112296f81a --- /dev/null +++ b/usr.bin/mg/region.c @@ -0,0 +1,291 @@ +/* + * 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 "def.h" + +/* + * Kill the region. Ask "getregion" + * to figure out the bounds of the region. + * Move "." to the start, and kill the characters. + */ +/*ARGSUSED*/ +killregion(f, n) +{ + register int s; + REGION region; + + if ((s=getregion(®ion)) != TRUE) + return (s); + if ((lastflag&CFKILL) == 0) /* This is a kill type */ + kdelete(); /* command, so do magic */ + thisflag |= CFKILL; /* kill buffer stuff. */ + curwp->w_dotp = region.r_linep; + curwp->w_doto = region.r_offset; + return (ldelete(region.r_size, KFORW)); +} + +/* + * Copy all of the characters in the + * region to the kill buffer. Don't move dot + * at all. This is a bit like a kill region followed + * by a yank. + */ +/*ARGSUSED*/ +copyregion(f, n) +{ + register LINE *linep; + register int loffs; + register int s; + REGION region; + VOID kdelete(); + + if ((s=getregion(®ion)) != TRUE) + return s; + if ((lastflag&CFKILL) == 0) /* Kill type command. */ + kdelete(); + thisflag |= CFKILL; + linep = region.r_linep; /* Current line. */ + loffs = region.r_offset; /* Current 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; + } + } + 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*/ +lowerregion(f, n) +{ + register LINE *linep; + register int loffs; + register int c; + register int s; + REGION region; + + if ((s=getregion(®ion)) != TRUE) + return s; + lchange(WFHARD); + 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*/ +upperregion(f, n) +{ + register LINE *linep; + register int loffs; + register int c; + register int s; + REGION region; + VOID lchange(); + + if ((s=getregion(®ion)) != TRUE) + return s; + lchange(WFHARD); + 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 clobberring" flag. + */ +getregion(rp) register REGION *rp; { + register LINE *flp; + register LINE *blp; + register long fsize; /* Long now. */ + register long bsize; + + if (curwp->w_markp == NULL) { + ewprintf("No mark set in this window"); + return (FALSE); + } + if (curwp->w_dotp == curwp->w_markp) { /* "r_size" always ok. */ + rp->r_linep = curwp->w_dotp; + 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; + } + flp = blp = curwp->w_dotp; /* Get region size. */ + bsize = curwp->w_doto; + fsize = llength(flp)-curwp->w_doto+1; + while (lforw(flp)!=curbp->b_linep || lback(blp)!=curbp->b_linep) { + if (lforw(flp) != curbp->b_linep) { + flp = lforw(flp); + if (flp == curwp->w_markp) { + rp->r_linep = curwp->w_dotp; + rp->r_offset = curwp->w_doto; + return (setsize(rp, + (RSIZE) (fsize+curwp->w_marko))); + } + fsize += llength(flp)+1; + } + if (lback(blp) != curbp->b_linep) { + blp = lback(blp); + bsize += llength(blp)+1; + if (blp == curwp->w_markp) { + rp->r_linep = blp; + rp->r_offset = curwp->w_marko; + return (setsize(rp, + (RSIZE) (bsize-curwp->w_marko))); + } + } + } + ewprintf("Bug: lost mark"); /* Gak! */ + return FALSE; +} + +/* + * Set size, and check for overflow. + */ +setsize(rp, size) register REGION *rp; register RSIZE size; { + + rp->r_size = size; + if (rp->r_size != size) { + ewprintf("Region is too large"); + return FALSE; + } + return TRUE; +} + +#ifdef PREFIXREGION +/* + * Implements one of my favorite keyboard macros; put a string at the + * beginning of a number of lines in a buffer. The quote string is + * settable by using set-prefix-string. Great for quoting mail, which + * is the real reason I wrote it, but also has uses for creating bar + * comments (like the one you're reading) in C code. + */ + +#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*/ +prefixregion(f, n) +{ + register int s; + register LINE *first, *last; + register int nline; + REGION region; + char *prefix = prefix_string; + + if ((f == TRUE) && ((s = setprefix(FFRAND, 1)) != TRUE)) + return s; + + /* get # of lines to affect */ + if ((s = getregion(®ion)) != 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; + + /* 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 prefix string. + */ + +/*ARGSUSED*/ +setprefix(f, n) +{ + char buf[PREFIXLENGTH]; + register int s; + + if (prefix_string[0] == '\0') + s = ereply("Prefix string: ",buf,sizeof buf); + else + s = ereply("Prefix string (default %s): ", + buf,sizeof buf,prefix_string); + if (s == TRUE) + (VOID) strcpy(prefix_string, buf); + if ((s == FALSE) && (prefix_string[0] != '\0')) /* CR -- use old one */ + s = TRUE; + return s; +} +#endif diff --git a/usr.bin/mg/search.c b/usr.bin/mg/search.c new file mode 100644 index 00000000000..3d127ce1f02 --- /dev/null +++ b/usr.bin/mg/search.c @@ -0,0 +1,664 @@ +/* + * Search commands. + * The functions in this file implement the + * search commands (both plain and incremental searches + * are supported) and the query-replace command. + * + * The plain old search code is part of the original + * MicroEMACS "distribution". The incremental search code, + * and the query-replace code, is by Rich Ellison. + */ +#include "def.h" +#ifndef NO_MACRO +#include "macro.h" +#endif + +#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) + +typedef struct { + int s_code; + LINE *s_dotp; + int s_doto; +} SRCHCOM; + +static SRCHCOM cmds[NSRCH]; +static int cip; + +int srch_lastdir = SRCH_NOPR; /* Last search flags. */ + +static VOID is_cpush(); +static VOID is_lpush(); +static VOID is_pop(); +static int is_peek(); +static VOID is_undo(); +static int is_find(); +static VOID is_prompt(); +static VOID is_dspl(); +static int eq(); + +/* + * Search forward. + * Get a search string from the user, and search for it, + * starting at ".". If found, "." gets moved to just after the + * matched characters, and display does all the hard stuff. + * If not found, it just prints a message. + */ +/*ARGSUSED*/ +forwsearch(f, n) +{ + register int s; + + if ((s=readpattern("Search")) != TRUE) + return s; + if (forwsrch() == FALSE) { + ewprintf("Search failed: \"%s\"", pat); + return FALSE; + } + 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*/ +backsearch(f, n) +{ + register int s; + + if ((s=readpattern("Search backward")) != TRUE) + return (s); + if (backsrch() == FALSE) { + ewprintf("Search failed: \"%s\"", pat); + return FALSE; + } + 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. + */ +/*ARGSUSED*/ +searchagain(f, n) +{ + if (srch_lastdir == SRCH_FORW) { + if (forwsrch() == FALSE) { + ewprintf("Search failed: \"%s\"", pat); + return FALSE; + } + return TRUE; + } + if (srch_lastdir == SRCH_BACK) { + if (backsrch() == FALSE) { + ewprintf("Search failed: \"%s\"", pat); + return FALSE; + } + return TRUE; + } + ewprintf("No last search"); + return FALSE; +} + +/* + * Use incremental searching, initially in the forward direction. + * isearch ignores any explicit arguments. + */ +/*ARGSUSED*/ +forwisearch(f, n) +{ + return isearch(SRCH_FORW); +} + +/* + * Use incremental searching, initially in the reverse direction. + * isearch ignores any explicit arguments. + */ +/*ARGSUSED*/ +backisearch(f, n) +{ + return isearch(SRCH_BACK); +} + +/* + * Incremental Search. + * dir is used as the initial direction to search. + * ^S switch direction to forward + * ^R switch direction to reverse + * ^Q quote next character (allows searching for ^N etc.) + * <ESC> exit from Isearch + * <DEL> undoes last character typed. (tricky job to do this correctly). + * other ^ exit search, don't set mark + * else accumulate into search string + */ +isearch(dir) { + register int c; + register LINE *clp; + register int cbo; + register int success; + int pptr; + char opat[NPAT]; + VOID ungetkey(); + +#ifndef NO_MACRO + if(macrodef) { + ewprintf("Can't isearch in macro"); + return FALSE; + } +#endif + for (cip=0; cip<NSRCH; cip++) + cmds[cip].s_code = SRCH_NOPR; + (VOID) strcpy(opat, pat); + cip = 0; + pptr = -1; + clp = curwp->w_dotp; + cbo = curwp->w_doto; + is_lpush(); + is_cpush(SRCH_BEGIN); + success = TRUE; + is_prompt(dir, TRUE, success); + for (;;) { + update(); + switch (c = getkey(FALSE)) { + case CCHR('['): + srch_lastdir = dir; + curwp->w_markp = clp; + curwp->w_marko = cbo; + ewprintf("Mark set"); + return (TRUE); + + case CCHR('G'): + if (success != TRUE) { + while (is_peek() == SRCH_ACCM) + is_undo(&pptr, &dir); + success = TRUE; + is_prompt(dir, pptr < 0, success); + break; + } + curwp->w_dotp = clp; + curwp->w_doto = cbo; + curwp->w_flag |= WFMOVE; + srch_lastdir = dir; + (VOID) ctrlg(FFRAND, 0); + (VOID) strcpy(pat, opat); + return ABORT; + + case CCHR(']'): + case CCHR('S'): + if (dir == SRCH_BACK) { + dir = SRCH_FORW; + is_lpush(); + is_cpush(SRCH_FORW); + success = TRUE; + } + if (success==FALSE && dir==SRCH_FORW) + break; + is_lpush(); + pptr = strlen(pat); + (VOID) forwchar(FFRAND, 1); + if (is_find(SRCH_FORW) != FALSE) is_cpush(SRCH_MARK); + else { + (VOID) backchar(FFRAND, 1); + ttbeep(); + success = FALSE; + } + is_prompt(dir, pptr < 0, success); + break; + + case CCHR('R'): + if (dir == SRCH_FORW) { + dir = SRCH_BACK; + is_lpush(); + is_cpush(SRCH_BACK); + success = TRUE; + } + if (success==FALSE && dir==SRCH_BACK) + break; + is_lpush(); + pptr = strlen(pat); + (VOID) backchar(FFRAND, 1); + if (is_find(SRCH_BACK) != FALSE) is_cpush(SRCH_MARK); + else { + (VOID) forwchar(FFRAND, 1); + ttbeep(); + success = FALSE; + } + is_prompt(dir, pptr < 0, success); + break; + + case CCHR('H'): + case CCHR('?'): + is_undo(&pptr, &dir); + if (is_peek() != SRCH_ACCM) success = TRUE; + is_prompt(dir, pptr < 0, success); + break; + + case CCHR('\\'): + case CCHR('Q'): + c = (char) getkey(FALSE); + goto addchar; + case CCHR('M'): + c = CCHR('J'); + goto addchar; + + default: + if (ISCTRL(c)) { + ungetkey(c); + curwp->w_markp = clp; + curwp->w_marko = cbo; + ewprintf("Mark set"); + curwp->w_flag |= WFMOVE; + return TRUE; + } /* and continue */ + case CCHR('I'): + case CCHR('J'): + addchar: + if (pptr == -1) + pptr = 0; + if (pptr == 0) + success = TRUE; + pat[pptr++] = c; + if (pptr == NPAT) { + ewprintf("Pattern too long"); + return FALSE; + } + pat[pptr] = '\0'; + is_lpush(); + if (success != FALSE) { + if (is_find(dir) != FALSE) + is_cpush(c); + else { + success = FALSE; + ttbeep(); + is_cpush(SRCH_ACCM); + } + } else + is_cpush(SRCH_ACCM); + is_prompt(dir, FALSE, success); + } + } + /*NOTREACHED*/ +} + +static VOID +is_cpush(cmd) register int cmd; { + if (++cip >= NSRCH) + cip = 0; + cmds[cip].s_code = cmd; +} + +static VOID +is_lpush() { + register int ctp; + + ctp = cip+1; + if (ctp >= NSRCH) + ctp = 0; + cmds[ctp].s_code = SRCH_NOPR; + cmds[ctp].s_doto = curwp->w_doto; + cmds[ctp].s_dotp = curwp->w_dotp; +} + +static VOID +is_pop() { + if (cmds[cip].s_code != SRCH_NOPR) { + curwp->w_doto = cmds[cip].s_doto; + curwp->w_dotp = cmds[cip].s_dotp; + curwp->w_flag |= WFMOVE; + cmds[cip].s_code = SRCH_NOPR; + } + if (--cip <= 0) + cip = NSRCH-1; +} + +static int +is_peek() { + return cmds[cip].s_code; +} + +/* this used to always return TRUE (the return value was checked) */ +static VOID +is_undo(pptr, dir) register int *pptr; register int *dir; { + register int redo = FALSE ; + switch (cmds[cip].s_code) { + case SRCH_BEGIN: + case SRCH_NOPR: + *pptr = -1; + case SRCH_MARK: + break; + + case SRCH_FORW: + *dir = SRCH_BACK; + redo = TRUE; + break; + + case SRCH_BACK: + *dir = SRCH_FORW; + redo = TRUE; + break; + + case SRCH_ACCM: + default: + *pptr -= 1; + if (*pptr < 0) + *pptr = 0; + pat[*pptr] = '\0'; + break; + } + is_pop(); + if (redo) is_undo(pptr, dir); +} + +static int +is_find(dir) register int dir; { + register int plen, odoto; + register LINE *odotp ; + + odoto = curwp->w_doto; + odotp = curwp->w_dotp; + plen = strlen(pat); + if (plen != 0) { + if (dir==SRCH_FORW) { + (VOID) backchar(FFARG | FFRAND, plen); + if (forwsrch() == FALSE) { + curwp->w_doto = odoto; + curwp->w_dotp = odotp; + return FALSE; + } + return TRUE; + } + if (dir==SRCH_BACK) { + (VOID) forwchar(FFARG | FFRAND, plen); + if (backsrch() == FALSE) { + curwp->w_doto = odoto; + curwp->w_dotp = odotp; + return FALSE; + } + return TRUE; + } + ewprintf("bad call to is_find"); + return FALSE; + } + return FALSE; +} + +/* + * If called with "dir" not one of SRCH_FORW + * or SRCH_BACK, this routine used to print an error + * message. It also used to return TRUE or FALSE, + * depending on if it liked the "dir". However, none + * of the callers looked at the status, so I just + * made the checking vanish. + */ +static VOID +is_prompt(dir, flag, success) { + if (dir == SRCH_FORW) { + if (success != FALSE) + is_dspl("I-search", flag); + else + is_dspl("Failing I-search", flag); + } else if (dir == SRCH_BACK) { + if (success != FALSE) + is_dspl("I-search backward", flag); + else + is_dspl("Failing I-search backward", flag); + } else ewprintf("Broken call to is_prompt"); +} + +/* + * Prompt writing routine for the incremental search. + * The "prompt" is just a string. The "flag" determines + * whether pat should be printed. + */ +static VOID +is_dspl(prompt, flag) char *prompt; { + + if (flag != FALSE) + ewprintf("%s: ", prompt); + else + ewprintf("%s: %s", prompt, pat); +} + +/* + * Query Replace. + * Replace strings selectively. Does a search and replace operation. + */ +/*ARGSUSED*/ +queryrepl(f, n) +{ + register int s; + register int rcnt = 0; /* Replacements made so far */ + register int plen; /* length of found string */ + char news[NPAT]; /* replacement string */ + +#ifndef NO_MACRO + if(macrodef) { + ewprintf("Can't query replace in macro"); + return FALSE; + } +#endif + if ((s=readpattern("Query replace")) != TRUE) + return (s); + if ((s=ereply("Query replace %s with: ",news, NPAT, pat)) == ABORT) + return (s); + if (s == FALSE) + news[0] = '\0'; + ewprintf("Query replacing %s with %s:", pat, news); + plen = strlen(pat); + + /* + * 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 (forwsrch() == TRUE) { + retry: + update(); + switch (getkey(FALSE)) { + case ' ': + if (lreplace((RSIZE) plen, news, f) == FALSE) + return (FALSE); + rcnt++; + break; + + case '.': + if (lreplace((RSIZE) plen, news, f) == FALSE) + return (FALSE); + rcnt++; + goto stopsearch; + + case CCHR('G'): /* ^G or ESC */ + (VOID) ctrlg(FFRAND, 0); + case CCHR('['): + goto stopsearch; + + case '!': + do { + if (lreplace((RSIZE) plen, news, f) == FALSE) + return (FALSE); + rcnt++; + } while (forwsrch() == TRUE); + goto stopsearch; + + case CCHR('H'): + case CCHR('?'): /* To not replace */ + break; + + default: +ewprintf("<SP> replace, [.] rep-end, <DEL> don't, [!] repl rest <ESC> quit"); + goto retry; + } + } +stopsearch: + curwp->w_flag |= WFHARD; + update(); + if (rcnt == 0) + ewprintf("(No replacements done)"); + else if (rcnt == 1) + ewprintf("(1 replacement done)"); + else + ewprintf("(%d replacements done)", rcnt); + return TRUE; +} + +/* + * 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. + */ +forwsrch() { + register LINE *clp; + register int cbo; + register LINE *tlp; + register int tbo; + char *pp; + register int c; + + clp = curwp->w_dotp; + cbo = curwp->w_doto; + for(;;) { + if (cbo == llength(clp)) { + if((clp = lforw(clp)) == curbp->b_linep) break; + cbo = 0; + c = CCHR('J'); + } else + c = lgetc(clp, cbo++); + if (eq(c, pat[0]) != FALSE) { + tlp = clp; + tbo = cbo; + pp = &pat[1]; + while (*pp != 0) { + if (tbo == llength(tlp)) { + tlp = lforw(tlp); + if (tlp == curbp->b_linep) + goto fail; + tbo = 0; + c = CCHR('J'); + } else + c = lgetc(tlp, tbo++); + if (eq(c, *pp++) == FALSE) + goto fail; + } + curwp->w_dotp = tlp; + curwp->w_doto = tbo; + curwp->w_flag |= WFMOVE; + return TRUE; + } + fail: ; + } + return FALSE; +} + +/* + * This routine does the real work of a + * backward 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. + */ +backsrch() { + register LINE *clp; + register int cbo; + register LINE *tlp; + register int tbo; + register int c; + register char *epp; + register char *pp; + + for (epp = &pat[0]; epp[1] != 0; ++epp) + ; + clp = curwp->w_dotp; + cbo = curwp->w_doto; + for (;;) { + if (cbo == 0) { + clp = lback(clp); + if (clp == curbp->b_linep) + return FALSE; + cbo = llength(clp)+1; + } + if (--cbo == llength(clp)) + c = CCHR('J'); + else + c = lgetc(clp,cbo); + if (eq(c, *epp) != FALSE) { + tlp = clp; + tbo = cbo; + pp = epp; + while (pp != &pat[0]) { + if (tbo == 0) { + tlp = lback(tlp); + if (tlp == curbp->b_linep) + goto fail; + tbo = llength(tlp)+1; + } + if (--tbo == llength(tlp)) + c = CCHR('J'); + else + c = lgetc(tlp,tbo); + if (eq(c, *--pp) == FALSE) + goto fail; + } + curwp->w_dotp = tlp; + curwp->w_doto = tbo; + curwp->w_flag |= WFMOVE; + return TRUE; + } + fail: ; + } + /*NOTREACHED*/ +} + +/* + * Compare two characters. + * The "bc" comes from the buffer. + * It has its case folded out. The + * "pc" is from the pattern. + */ +static int +eq(bc, pc) +register int bc, pc; +{ + bc = CHARMASK(bc); + pc = CHARMASK(pc); + if (bc == pc) return TRUE; + if (ISUPPER(bc)) return TOLOWER(bc) == pc; + if (ISUPPER(pc)) return bc == TOLOWER(pc); + return FALSE; +} + +/* + * Read a pattern. + * Stash it in the external variable "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. + */ +readpattern(prompt) char *prompt; { + register int s; + char tpat[NPAT]; + + if (tpat[0] == '\0') s = ereply("%s: ", tpat, NPAT, prompt); + else s = ereply("%s: (default %s) ", tpat, NPAT, prompt, pat); + + if (s == TRUE) /* Specified */ + (VOID) strcpy(pat, tpat); + else if (s==FALSE && pat[0]!=0) /* CR, but old one */ + s = TRUE; + return s; +} diff --git a/usr.bin/mg/spawn.c b/usr.bin/mg/spawn.c new file mode 100644 index 00000000000..3d3f562b646 --- /dev/null +++ b/usr.bin/mg/spawn.c @@ -0,0 +1,120 @@ +/* + * Name: MicroGnuEmacs + * Spawn CLI for System V. + * + * Spawn for System V. + */ +#include "def.h" + +#include <signal.h> + +char *shellp = NULL; /* Saved "SHELL" program. */ +char *shname = NULL; /* Saved shell name */ + +extern char *getenv(); + +/* + * On System V, we no gots job control, so always run + * a subshell using fork/exec. Bound to "C-C", and used + * as a subcommand by "C-Z". (daveb) + * + * Returns 0 if the shell executed OK, something else if + * we couldn't start shell or it exited badly. + */ +spawncli(f, n) +{ + extern char *strrchr(); + register int pid; + register int wpid; + register int (*oqsig)(); + register int (*oisig)(); + int status; + int errp = FALSE; + + if (shellp == NULL) { + shellp = getenv("SHELL"); + if (shellp == NULL) + shellp = getenv("shell"); + if (shellp == NULL) + shellp = "/bin/sh"; /* Safer. */ + shname = strrchr( shellp, '/' ); + shname = shname ? shname++ : shellp; + + } + ttcolor(CTEXT); + ttnowindow(); + ttmove(nrow-1, 0); + if (epresf != FALSE) { + tteeol(); + epresf = FALSE; + } + ttclose(); + sgarbf = TRUE; /* Force repaint. */ + oqsig = signal(SIGQUIT, SIG_IGN); + oisig = signal(SIGINT, SIG_IGN); + if ((pid=fork()) == 0) { + (void) signal(SIGINT, oisig); + (void) signal(SIGQUIT, oqsig); + execlp(shellp, shname, "-i", (char *)NULL); + _exit(1); /* Should do better! */ + } + else if (pid > 0) { + while ((wpid=wait(&status))>=0 && wpid!=pid) + ; + } + else errp = TRUE; + + signal(SIGINT, oisig); + signal(SIGQUIT, oqsig); + ttopen(); + setttysize(); + ttwindow(); + + if(errp) + ewprintf("Failed to create process"); + + return ( errp | status ); +} + +/* + * Put the tty in normal mode, so he can do a second ^Z. Then + * wait for a char. To use ^Z^Z to suspend and "fg %mg CR CR" + * to continue; + * + * Returns 0 if it works, which presumably it must. + */ +attachtoparent(f, n) +{ + extern char *strrchr(); + register int pid; + register int wpid; + register int (*oqsig)(); + register int (*oisig)(); + int status; + int errp = FALSE; + int omask; + sigset_t newsig,oldsig; + + ttcolor(CTEXT); + ttnowindow(); + ttmove(nrow-1, 0); + if (epresf != FALSE) { + tteeol(); + epresf = FALSE; + } + ttclose(); + sgarbf = TRUE; /* Force repaint. */ +#ifdef SIGTSTP + sigemptyset(&newsig); + sigprocmask(SIG_SETMASK, &newsig, &oldsig); + (void) kill(0, SIGTSTP); + sigprocmask(SIG_SETMASK, &oldsig, NULL); +#else + getchar(); +#endif + ttopen(); + setttysize(); + ttwindow(); + + return ( 0 ); +} diff --git a/usr.bin/mg/sysdef.h b/usr.bin/mg/sysdef.h new file mode 100644 index 00000000000..9b0ac63ee31 --- /dev/null +++ b/usr.bin/mg/sysdef.h @@ -0,0 +1,37 @@ +/* + * System V system header file + */ +#include <stdio.h> + +#define KBLOCK 8192 /* Kill grow. */ +#define GOOD 0 /* Good exit status. */ +#define MAXPATH 256 /* Maximum length of path for chdir */ + +typedef long RSIZE; /* Type for file/region sizes */ +typedef short KCHAR; /* Type for internal keystrokes */ + +/* + * Macros used by the buffer name making code. + * Start at the end of the file name, scan to the left + * until BDC1 (or BDC2, if defined) is reached. The buffer + * name starts just to the right of that location, and + * stops at end of string (or at the next BDC3 character, + * if defined). BDC2 and BDC3 are mainly for VMS. + */ +#define BDC1 '/' /* Buffer names. */ + +#define MALLOCROUND(m) (m+=7,m&=~7) /* round up to 8 byte boundry */ + +#define fncmp strcmp /* file name comparison */ +#define bcopy(s,d,n) memcpy(d,s,n) /* memory-to-memory copy */ +#define bzero(s,n) memset(s,0,n) /* clear memory */ +char *getenv(); +#define gettermtype() getenv("TERM") /* determine terminal type */ +char *getcwd(); +#define getwd(cwd) getcwd(cwd,NFILEN) /* get current working dir */ + +struct fileinfo { + unsigned short fi_mode; + unsigned short fi_uid; + unsigned short fi_gid; +}; diff --git a/usr.bin/mg/tar.exclude b/usr.bin/mg/tar.exclude new file mode 100644 index 00000000000..019cfb531d5 --- /dev/null +++ b/usr.bin/mg/tar.exclude @@ -0,0 +1,7 @@ +hold +obj +sys +mg.tar +*.o +mg +*~ diff --git a/usr.bin/mg/tty.c b/usr.bin/mg/tty.c new file mode 100644 index 00000000000..3ec5c434629 --- /dev/null +++ b/usr.bin/mg/tty.c @@ -0,0 +1,442 @@ +/* + * Termcap/terminfo display driver + * + * Termcap is a terminal information database and routines to describe + * terminals on most UNIX systems. Many other systems have adopted + * this as a reasonable way to allow for widly 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 extensivly 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 assuption that scrolling + * region operations look better. + */ +#include "def.h" + +#define BEL 0x07 /* BEL character. */ + +extern int ttrow; +extern int ttcol; +extern int tttop; +extern int ttbot; +extern int tthue; + +int tceeol; /* Costs are set later */ +int tcinsl; +int tcdell; + +static int insdel; /* Do we have both insert & delete line? */ + +#ifdef NO_RESIZE +static setttysize(); +#endif + +char *tgetstr(); +char *tgoto(); +int ttputc(); + +#define TCAPSLEN 1024 + +char tcapbuf[TCAPSLEN]; + +/* PC, UP, and BC are used by termlib, so must be extern and have these + * names unless you have a non-standard termlib. + */ + +int LI; /* standard # lines */ +char PC, + *CM, + *CE, + *UP, + *BC, + *IM, /* insert mode */ + *IC, /* insert a single space */ + *EI, /* end insert mode */ + *DC, + *AL, /* add line */ + *DL, /* del line */ + *pAL, /* parameterized add line */ + *pDL, /* parameterized delete line */ + *TI, /* term init -- start using cursor motion */ + *TE, /* term end --- end using cursor motion */ + *SO, + *SE, + *CD, + *CS, /* set scroll region */ + *SF, /* forw index (used with scroll region) */ + *SR; /* back index (used with scroll region) */ +#ifdef XKEYS +char *KS, *KE; /* enter keypad mode, exit keypad mode */ +#endif +int SG; /* number of glitches, 0 for invisible, -1 for none */ + /* (yes virginia, there are terminals with invisible glitches) */ + +/* + * Initialize the terminal when the editor + * gets started up. + */ +static char tcbuf[1024]; + +ttinit() { + char *tv_stype; + char *t, *p, *tgetstr(); +#ifndef gettermtype /* (avoid declaration if #define) */ + char *gettermtype(); /* system dependent function to determin terminal type */ +#endif + + if((tv_stype = gettermtype()) == NULL) + panic("Could not determine terminal type"); + if((tgetent(tcbuf, tv_stype)) != 1) { + (VOID) strcpy(tcbuf, "Unknown terminal type "); + (VOID) strcat(tcbuf, tv_stype); + panic(tcbuf); + } + + p = tcapbuf; + t = tgetstr("pc", &p); + if(t) PC = *t; + + LI = tgetnum("li"); + CD = tgetstr("cd", &p); + CM = tgetstr("cm", &p); + CE = tgetstr("ce", &p); + UP = tgetstr("up", &p); + BC = tgetstr("bc", &p); + IM = tgetstr("im", &p); + IC = tgetstr("ic", &p); + EI = tgetstr("ei", &p); + DC = tgetstr("dc", &p); + AL = tgetstr("al", &p); + DL = tgetstr("dl", &p); + pAL= tgetstr("AL", &p); /* parameterized insert and del. line */ + pDL= tgetstr("DL", &p); + TI = tgetstr("ti", &p); + TE = tgetstr("te", &p); + SO = tgetstr("so", &p); + SE = tgetstr("se", &p); + CS = tgetstr("cs", &p); /* set scrolling region */ + SF = tgetstr("sf", &p); + if(!SF || !*SF) { /* this is what GNU Emacs does */ + SF = tgetstr("do", &p); + if(!SF || !*SF) { + SF = tgetstr("nl", &p); + if(!SF || !*SF) SF = "\n"; + } + } + SR = tgetstr("sr", &p); + SG = tgetnum("sg"); /* standout glitch */ +#ifdef XKEYS + KS = tgetstr("ks", &p); /* keypad start, keypad end */ + KE = tgetstr("ke", &p); +#endif + + if(CM == NULL || UP == NULL) + panic("This terminal is to stupid to run MicroGnuEmacs\n"); + ttresize(); /* set nrow & ncol */ + + /* watch out for empty capabilities (sure to be wrong) */ + if (CE && !*CE) CE = NULL; + if (CS && !*CS) CS = NULL; + if (SR && !*SR) SR = NULL; + if (AL && !*AL) AL = NULL; + if (DL && !*DL) DL = NULL; + if (pAL && !*pAL) pAL = NULL; + if (pDL && !*pDL) pDL = NULL; + if (CD && !*CD) CD = NULL; + + if(!CE) tceeol = ncol; + else tceeol = charcost(CE); + + /* Estimate cost of inserting a line */ + if (CS && SR) tcinsl = charcost(CS)*2 + charcost(SR); + else if (pAL) tcinsl = charcost(pAL); + else if (AL) tcinsl = charcost(AL); + else tcinsl = NROW * NCOL; /* make this cost high enough */ + + /* Estimate cost of deleting a line */ + if (CS) tcdell = charcost(CS)*2 + charcost(SF); + else if (pDL) tcdell = charcost(pDL); + else if (DL) tcdell = charcost(DL); + else tcdell = NROW * NCOL; /* make this cost high enough */ + + /* Flag to indicate that we can both insert and delete lines */ + insdel = (AL || pAL) && (DL || pDL); + + if (p >= &tcapbuf[TCAPSLEN]) + panic("Terminal description too big!\n"); + if (TI && *TI) putpad(TI, 1); /* init the term */ +} + +/* + * 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. + */ +tttidy() { + if (TE && *TE) putpad(TE, 1); /* set the term back to normal mode */ +#ifdef XKEYS + ttykeymaptidy(); +#endif +} + +/* + * 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! + */ +ttmove(row, col) { + char *tgoto(); + + if (ttrow!=row || ttcol!=col) { + putpad(tgoto(CM, col, row), 1); + ttrow = row; + ttcol = col; + } +} + +/* + * Erase to end of line. + */ +tteeol() { + if(CE) putpad(CE, 1); + else { + register int i=ncol-ttcol; + while(i--) ttputc(' '); + ttrow = ttcol = HUGE; + } +} + +/* + * Erase to end of page. + */ +tteeop() { + if(CD) putpad(CD, nrow - ttrow); + else { + putpad(CE, 1); + if (insdel) ttdell(ttrow + 1, LI, LI - ttrow - 1); + else { /* do it by hand */ + register int line; + for (line = ttrow + 1; line <= LI; ++line) { + ttmove(line, 0); + tteeol(); + } + } + ttrow = ttcol = HUGE; + } +} + +/* + * Make a noise. + */ +ttbeep() { + ttputc(BEL); + 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 no scrolling region, use a set + * of insert and delete line sequences + */ +ttinsl(row, bot, nchunk) { + register int i, nl; + + if (row == bot) { /* Case of one line insert is */ + ttmove(row, 0); /* special */ + tteeol(); + return; + } + if (CS && SR) { /* Use scroll region and back index */ + nl = bot - row; + ttwindow(row,bot); + ttmove(row, 0); + while (nchunk--) putpad(SR, nl); + ttnowindow(); + return; + } else if (insdel) { + ttmove(1+bot-nchunk, 0); + nl = nrow - ttrow; + if (pDL) putpad(tgoto(pDL, 0, nchunk), nl); + else for (i=0; i<nchunk; i++) /* For all lines in the chunk */ + putpad(DL, nl); + ttmove(row, 0); + nl = nrow - ttrow; /* ttmove() changes ttrow */ + if (pAL) putpad(tgoto(pAL, 0, nchunk), nl); + else for (i=0; i<nchunk; i++) /* For all lines in the chunk */ + putpad(AL, 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 a crafty sequences of insert and delete + * lines. The presence of the echo area makes a + * boundry condition go away. + */ +ttdell(row, bot, nchunk) +{ + register int i, nl; + + if (row == bot) { /* One line special case */ + ttmove(row, 0); + tteeol(); + return; + } + if (CS) { /* scrolling region */ + nl = bot - row; + ttwindow(row, bot); + ttmove(bot, 0); + while (nchunk--) putpad(SF, nl); + ttnowindow(); + } + else if(insdel) { + ttmove(row, 0); /* Else use insert/delete line */ + nl = nrow - ttrow; + if (pDL) putpad(tgoto(pDL, 0, nchunk), nl); + else for (i=0; i<nchunk; i++) /* For all lines in the chunk */ + putpad(DL, nl); + ttmove(1+bot-nchunk,0); + nl = nrow - ttrow; /* ttmove() changes ttrow */ + if (pAL) putpad(tgoto(pAL, 0, nchunk), nl); + else for (i=0; i<nchunk; i++) /* For all lines in the chunk */ + putpad(AL, 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 pathalogical 1 line scroll window that + * 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). + * + */ +ttwindow(top, bot) +{ + if (CS && (tttop!=top || ttbot!=bot)) { + putpad(tgoto(CS, 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 is suspends the + * editor, and by "display.c" when it is getting ready + * to exit. This function gets to full screen scroll + * by telling the terminal to set a scrolling regin + * that is LI or nrow rows high, whichever is larger. + * This behavior seems to work right on systems + * where you can set your terminal size. + */ +ttnowindow() +{ + if (CS) { + putpad(tgoto(CS, (nrow > LI ? nrow : LI) - 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. + */ +ttcolor(color) register int color; { + if (color != tthue) { + if (color == CTEXT) { /* Normal video. */ + putpad(SE, 1); + } else if (color == CMODE) { /* Reverse video. */ + putpad(SO, 1); + } + tthue = color; /* Save the color. */ + } +} + +/* + * This routine is called by the + * "refresh the screen" command to try and resize + * the display. The new size, which must be deadstopped + * to not exceed the NROW and NCOL limits, it stored + * back into "nrow" and "ncol". Display can always deal + * with a screen NROW by NCOL. Look in "window.c" to + * see how the caller deals with a change. + */ +ttresize() { + setttysize(); /* found in "ttyio.c", */ + /* ask OS for tty size */ + if (nrow < 1) /* Check limits. */ + nrow = 1; + else if (nrow > NROW) + nrow = NROW; + if (ncol < 1) + ncol = 1; + else if (ncol > NCOL) + ncol = NCOL; +} + +#ifdef NO_RESIZE +static setttysize() { + nrow = tgetnum("li"); + ncol = tgetnum("co"); +} +#endif + +static int cci; + +/*ARGSUSED*/ +static int /* fake char output for charcost() */ +fakec(c) +char c; +{ + cci++; +} + +/* calculate the cost of doing string s */ +charcost (s) char *s; { + cci = 0; + + tputs(s, nrow, fakec); + return cci; +} diff --git a/usr.bin/mg/ttydef.h b/usr.bin/mg/ttydef.h new file mode 100644 index 00000000000..3700ea0f174 --- /dev/null +++ b/usr.bin/mg/ttydef.h @@ -0,0 +1,25 @@ +/* + * Termcap terminal file, nothing special, just make it big + * enough for windowing systems. + */ + +#define GOSLING /* Compile in fancy display. */ +/* #define MEMMAP */ /* Not memory mapped video. */ + +#define NROW 66 /* (maximum) Rows. */ +#define NCOL 132 /* (maximum) Columns. */ +/* #define MOVE_STANDOUT /* don't move in standout mode */ +#define STANDOUT_GLITCH /* possible standout glitch */ +#define TERMCAP /* for possible use in ttyio.c */ + +#define getkbd() (ttgetc()) + +#ifndef XKEYS +#define ttykeymapinit() {} +#endif + +extern int tputs(); +#define putpad(str, num) tputs(str, num, ttputc) + +#define KFIRST K00 +#define KLAST K00 diff --git a/usr.bin/mg/ttyio.c b/usr.bin/mg/ttyio.c new file mode 100644 index 00000000000..c3ddf420ef9 --- /dev/null +++ b/usr.bin/mg/ttyio.c @@ -0,0 +1,265 @@ +/* + * Name: MicroEMACS + * System V terminal I/O. + * Version: 0 + * Last edit: Tue Aug 26 23:57:57 PDT 1986 + * By: gonzo!daveb + * {sun, amdahl, mtxinu}!rtech!gonzo!daveb + * + * The functions in this file + * negotiate with the operating system for + * keyboard characters, and write characters to + * the display in a barely buffered fashion. + * + * This version goes along with tty/termcap/tty.c. + * Terminal size is determined there, rather than here, and + * this does not open the termcap file + */ +#include "def.h" + +#include <sys/types.h> +#include <fcntl.h> +#include <termio.h> + +#define NOBUF 512 /* Output buffer size. */ + +char obuf[NOBUF]; /* Output buffer. */ +int nobuf; /* buffer count */ + +static struct termio ot; /* entry state of the terminal */ +static struct termio nt; /* editor's terminal state */ + +static int ttyactivep = FALSE; /* terminal in editor mode? */ +static int ttysavedp = FALSE; /* terminal state saved? */ + +int nrow; /* Terminal size, rows. */ +int ncol; /* Terminal size, columns. */ + +/* These are used to implement typeahead on System V */ + +int kbdflgs; /* saved keyboard fd flags */ +int kbdpoll; /* in O_NDELAY mode */ +int kbdqp; /* there is a char in kbdq */ +char kbdq; /* char we've already read */ + +/* + * This function gets called once, to set up + * the terminal channel. This version turns off flow + * control. This may be wrong for your system, but no + * good solution has really been found (daveb). + */ +ttopen() +{ + register char *cp; + extern char *getenv(); + + if (ttyactivep) + return; + + if( !ttysavedp ) + { + if (ioctl(0, TCGETA, &ot) < 0) + abort(); + nt = ot; /* save entry state */ + nt.c_cc[VMIN] = 1; /* one character read is OK */ + nt.c_cc[VTIME] = 0; /* Never time out. */ + nt.c_iflag |= IGNBRK; + nt.c_iflag &= ~( ICRNL | INLCR | ISTRIP | IXON | IXOFF ); + nt.c_oflag &= ~OPOST; + nt.c_cflag |= CS8; /* allow 8th bit on input */ + nt.c_cflag &= ~PARENB; /* Don't check parity */ + nt.c_lflag &= ~( ECHO | ICANON | ISIG ); + + kbdpoll = (((kbdflgs = fcntl(0, F_GETFL, 0)) & O_NDELAY) != 0); + + ttysavedp = TRUE; + } + + if (ioctl(0, TCSETAF, &nt) < 0) + abort(); + + /* This really belongs in tty/termcap... */ + + if ((cp=getenv("TERMCAP")) == NULL + || (nrow=getvalue(cp, "li")) <= 0 + || (ncol=getvalue(cp, "co")) <= 0) { + nrow = 24; + ncol = 80; + } + if (nrow > NROW) /* Don't crash if the */ + nrow = NROW; /* termcap entry is */ + if (ncol > NCOL) /* too big. */ + ncol = NCOL; + + ttyactivep = TRUE; +} + +/* + * This routine scans a string, which is + * actually the return value of a getenv call for the TERMCAP + * variable, looking for numeric parameter "name". Return the value + * if found. Return -1 if not there. Assume that "name" is 2 + * characters long. This limited use of the TERMCAP lets us find + * out the size of a window on the X display. + */ +getvalue(cp, name) +register char *cp; +register char *name; +{ + for (;;) { + while (*cp!=0 && *cp!=':') + ++cp; + if (*cp++ == 0) /* Not found. */ + return (-1); + if (cp[0]==name[0] && cp[1]==name[1] && cp[2]=='#') + return (atoi(cp+3)); /* Stops on ":". */ + } +} + +/* + * This function gets called just + * before we go back home to the shell. Put all of + * the terminal parameters back. + */ +ttclose() +{ + if(!ttysavedp || !ttyactivep) + return; + ttflush(); + if (ioctl(0, TCSETAF, &ot) < 0 || fcntl( 0, F_SETFL, kbdflgs ) < 0) + abort(); + ttyactivep = FALSE; +} + +/* + * Write character to the display. + * Characters are buffered up, to make things + * a little bit more efficient. + */ +ttputc(c) +{ + if (nobuf >= NOBUF) + ttflush(); + obuf[nobuf++] = c; +} + +/* + * Flush output. + */ +ttflush() +{ + if (nobuf != 0) { + write(1, obuf, nobuf); + nobuf = 0; + } +} + +/* + * Read character from terminal. + * All 8 bits are returned, so that you can use + * a multi-national terminal. + * + * If keyboard 'queue' already has typeahead from a typeahead() call, + * just return it. Otherwise, make sure we are in blocking i/o mode + * and read a character. + */ +ttgetc() +{ + if( kbdqp ) + kbdqp = FALSE; + else + { + if( kbdpoll && fcntl( 0, F_SETFL, kbdflgs ) < 0 ) + abort(); + kbdpoll = FALSE; + while (read(0, &kbdq, 1) != 1) + ; + } + return ( kbdq & 0xff ); +} + +/* + * Return non-FALSE if typeahead is pending. + * + * If already got unread typeahead, do nothing. + * Otherwise, set keyboard to O_NDELAY if not already, and try + * a one character read. + */ +typeahead() +{ + if( !kbdqp ) + { + if( !kbdpoll && fcntl( 0, F_SETFL, kbdflgs | O_NDELAY ) < 0 ) + abort(); + kbdpoll = TRUE; + kbdqp = (1 == read( 0, &kbdq, 1 )); + } + return ( kbdqp ); +} + + +/* + * panic: print error and die, leaving core file. + * Don't know why this is needed (daveb). + */ +panic(s) +char *s; +{ + fprintf(stderr, "%s\r\n", s); + abort(); +} + + +/* +** This should check the size of the window, and reset if needed. +*/ + +setttysize() +{ +#ifdef TIOCGWINSZ + struct winsize winsize; + if (ioctl(0, TIOCGWINSZ, (char *) &winsize) == 0) { + nrow = winsize . ws_row; + ncol = winsize . ws_col; + } else +#endif + if ((nrow=tgetnum ("li")) <= 0 + || (ncol=tgetnum ("co")) <= 0) { + nrow = 24; + ncol = 80; + } + if (nrow > NROW) /* Don't crash if the */ + nrow = NROW; /* termcap entry is */ + if (ncol > NCOL) /* too big. */ + ncol = NCOL; +} + +#ifndef NO_DPROMPT +#include <signal.h> +#include <setjmp.h> + +static jmp_buf tohere; + +static void alrm() +{ + longjmp(tohere, -1); +} + +/* + * Return TRUE if we wait without doing anything, else return FALSE. + */ + +ttwait() +{ + int alrm(); + + if (kbdqp) + return FALSE; /* already pending input */ + if (setjmp(tohere)) + return TRUE; /* timeout on read if here */ + signal(SIGALRM, alrm); alarm(2); + kbdqp = (1 == read(0, &kbdq, 1)); + alarm(0); + return FALSE; /* successful read if here */ +} +#endif NO_DPROMPT diff --git a/usr.bin/mg/ttykbd.c b/usr.bin/mg/ttykbd.c new file mode 100644 index 00000000000..13c0198a080 --- /dev/null +++ b/usr.bin/mg/ttykbd.c @@ -0,0 +1,55 @@ +/* + * Name: MG 2a + * Termcap keyboard driver using key files + * Created: 22-Nov-1987 Mic Kaczmarczik (mic@emx.cc.utexas.edu) + */ + +#include "def.h" +#ifdef XKEYS + +/* + * 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 usefull other than to help debug FKEYS code in + * extend.c. + */ + +#ifdef FKEYS +char *keystrings[] = { NULL } ; +#endif + +/* + * Turn on function keys using KS, 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. + */ +extern int ttputc(); + +ttykeymapinit() +{ + extern char *KS; +#ifndef NO_STARTUP + char *cp, *startupfile(); + + if (cp = gettermtype()) { + if (((cp = startupfile(cp)) != NULL) + && (load(cp) != TRUE)) + ewprintf("Error reading key initialization file"); + } +#endif + if (KS && *KS) /* turn on keypad */ + putpad(KS, 1); +} + +/* + * Clean up the keyboard -- called by tttidy() + */ +ttykeymaptidy() +{ + extern char *KE; + + if (KE && *KE) + putpad(KE, 1); /* turn off keypad */ +} + +#endif diff --git a/usr.bin/mg/tutorial b/usr.bin/mg/tutorial new file mode 100644 index 00000000000..d623febf889 --- /dev/null +++ b/usr.bin/mg/tutorial @@ -0,0 +1,604 @@ +Copyright (c) 1985 Richard M. Stallman. See end for copying conditions. + +You are looking at the Emacs tutorial. + +Emacs commands generally involve the CONTROL key or the META (ESC) +key. Rather than write out META or CONTROL each time we want you to +prefix a character, we'll use the following abbreviations: + + C-<chr> means hold the CONTROL key while typing the character <chr> + Thus, C-f would be: hold the CONTROL key and type f. + M-<chr> means type <ESC>, release it, then type the character <chr>. + +The characters ">>" at the left margin indicate directions for you to +try using a command. For instance: + +>> Now type C-v (View next screen) to move to the next screen. + (go ahead, do it by depressing the control key and v together). + From now on, you'll be expected to do this whenever you finish + reading the screen. + +Note that there is an overlap when going from screen to screen; this +provides some continuity when moving through the file. + +The first thing that you need to know is how to move around from +place to place in the file. You already know how to move forward a +screen, with C-v. To move backwards a screen, type M-v (type <ESC>v). + +>> Try typing M-v and then C-v to move back and forth a few times. + + +SUMMARY +------- + +The following commands are useful for viewing screenfuls: + + C-v Move forward one screenful + M-v Move backward one screenful + C-l Clear screen and redisplay everything + putting the text near the cursor at the center. + (That's control-L, not control-1. + There is no such character as control-1.) + +>> Find the cursor and remember what text is near it. + Then type a C-l. + Find the cursor again and see what text is near it now. + + +BASIC CURSOR CONTROL +-------------------- + +Getting from screenful to screenful is useful, but how do you +reposition yourself within a given screen to a specific place? There +are several ways you can do this. One way (not the best, but the most +basic) is to use the commands previous, backward, forward and next. +As you can imagine these commands (which are given to Emacs as C-p, +C-b, C-f, and C-n respectively) move the cursor from where it +currently is to a new place in the given direction. It is also +possible to move the cursor with the arrow keys, but this requires you +move your hand from the keyboard, it is also not supported on other +machines that do support Emacs. Emacs runs on everything from a CP/M +machine to large mainframes. Here then, in a more graphical form are +the commands: + + Previous line, C-p + : + : + Backward, C-b .... Current cursor position .... Forward, C-f + : + : + Next line, C-n + +>> Move the cursor to the line in the middle of that diagram + and type C-l to see the whole diagram centered in the screen. + +You'll probably find it easy to think of these by letter. P for +previous, N for next, B for backward and F for forward. These are +the basic cursor positioning commands and you'll be using them ALL +the time so it would be of great benefit if you learn them now. + +>> Do a few C-n's to bring the cursor down to this line. + +>> Move into the line with C-f's and then up with C-p's. + See what C-p does when the cursor is in the middle of the line. + +>> Try to C-b at the beginning of a line. Do a few more C-b's. + Then do C-f's back to the end of the line and beyond. + +When you go off the top or bottom of the screen, the text beyond +the edge is shifted onto the screen so that your instructions can +be carried out while keeping the cursor on the screen. + +>> Try to move the cursor off the bottom of the screen with C-n and + see what happens. + +If moving by characters is too slow, you can move by words. M-f +(ESC-f) moves forward a word and M-b moves back a word. + +>> Type a few M-f's and M-b's. Intersperse them with C-f's and C-b's. + +Notice the parallel between C-f and C-b on the one hand, and M-f and +M-b on the other hand. Very often Meta characters are used for +operations related to English text whereas Control characters operate +on the basic textual units that are independent of what you are +editing (characters, lines, etc). C-a and C-e move to the beginning or +end of a line. + +>> Try a couple of C-a's, and then a couple of C-e's. + See how repeated C-a's do nothing. + +Two other simple cursor motion commands are M-< (Meta Less-than), +which moves to the beginning of the file, and M-> (Meta Greater-than), +which moves to the end of the file. You probably don't need to try +them, since finding this spot again will be boring. On most terminals +the "<" is above the comma and you must use the shift key to type it. +On these terminals you must use the shift key to type M-< also; +without the shift key, you would be typing M-comma. + +The location of the cursor in the text is also called "point". To +paraphrase, the cursor shows on the screen where point is located in +the text. + +Here is a summary of simple moving operations including the word and +sentence moving commands: + + C-f Move forward a character + C-b Move backward a character + + M-f Move forward a word + M-b Move backward a word + + c-n Move to next line + C-p Move to previous line + + C-a Move to beginning of line + C-e Move to end of line + + M-< Go to beginning of file + M-> Go to end of file + +>> Try all of these commands now a few times for practice. + Since the last two will take you away from this screen, + you can come back here with M-v's and C-v's. These are + the most often used commands. + +Like all other commands in Emacs, these commands can be given +arguments which cause them to be executed repeatedly. The way you +give a command a repeat count is by typing C-u and then the digits +before you type the command. + +For instance, C-u 8 C-f moves forward eight characters. + +>> Try giving a suitable argument to C-n or C-p to come as close + as you can to this line in one jump. + +The only apparent exception to this is the screen moving commands, +C-v and M-v. When given an argument, they scroll the screen up or +down by that many lines, rather than screenfuls. This proves to be +much more useful. + +>> Try typing C-u 8 C-v now. + +Did it scroll the screen up by 8 lines? If you would like to +scroll it down you can give an argument to M-v. + + +WHEN EMACS IS HUNG +----------------- + +If Emacs gets into an infinite (or simply very long) computation which +you don't want to finish, you can stop it safely by typing C-g. +You can also use C-g to discard a numeric argument or the beginning of +a command that you don't want to finish. + +>> Type C-u 100 to make a numeric arg of 100, then type C-g. + Now type C-f. How many characters does it move? + If you have typed an <ESC> by mistake, you can get rid of it + with a C-g. + +WINDOWS +------- + +Emacs can have several windows, each displaying its own text. +At this stage it is better not to go into the techniques of +using multiple windows. But you do need to know how to get +rid of extra windows that may appear to display help or +output from certain commands. It is simple: + + C-x 1 One window (i.e., kill all other windows). + +That is Control-x followed by the digit 1. +C-x 1 makes the window which the cursor is in become +the full screen, by getting rid of any other windows. + +>> Move the cursor to this line and type C-l (Control-L). +>> Type M-x. The cursor will move to the bottom of the screen. +>> Type the words "describe-bindings" and hit return. + See how this window shrinks, while a new one appears + to display which functions are connected to which keys. + +>> Type C-x 1 and see the documentation listing window disappear. + + +INSERTING AND DELETING +---------------------- + +If you want to insert text, just type it. Characters which you can +see, such as A, 7, *, etc. are taken by Emacs as text and inserted +immediately. Type <Return> (the carriage-return key) to insert a +Newline character. + +You can delete the last character you typed by typing <DEL>. More +generally, <DEL> deletes the character immediately before the current +cursor position. + +>> Do this now, type a few characters and then delete them + by typing <DEL> a few times. Don't worry about this file + being changed; you won't affect the master tutorial. This is just + a copy of it. + +>> Now start typing text until you reach the right margin, and keep + typing. When a line of text gets too big for one line on the + screen, the line of text is "continued" off the edge of the screen. + The dollar sign at the right margin indicates a line which has + been continued. +>> Use <DEL>s to delete the text until the line fits on one screen + line again. The continuation mark goes away. + +>> Move the cursor to the beginning of a line and type <DEL>. This + deletes the newline before the line and merges the line onto + the previous line. The resulting line may be too long to fit, in + which case it has a continuation mark. +>> Type <Return> to reinsert the Newline you deleted. + +Remember that most Emacs commands can be given a repeat count; +this includes characters which insert themselves. + +>> Try that now -- type C-u 8 * and see what happens. + +You've now learned the most basic way of typing something in +Emacs and correcting errors. You can delete by words or lines +as well. Here is a summary of the delete operations: + + <DEL> delete the character just before the cursor + C-d delete the next character after the cursor + + M-<DEL> kill the word immediately before the cursor + M-d kill the next word after the cursor + + C-k kill from the cursor position to end of line + +Notice that <DEL> and C-d vs M-<DEL> and M-d extend the parallel +started by C-f and M-f (well, <DEL> isn't really a control +character, but let's not worry about that). + +Now suppose you kill something, and then you decide that you want to +get it back? Well, whenever you kill something bigger than a +character, Emacs saves it for you. To yank it back, use C-y. You +can kill text in one place, move elsewhere, and then do C-y; this is +a good way to move text around. Note that the difference +between "Killing" and "Deleting" something is that "Killed" things +can be yanked back, and "Deleted" things cannot. Generally, the +commands that can destroy a lot of text save it, while the ones that +attack only one character, or nothing but blank lines and spaces, do +not save. + +For instance, type C-n a couple times to postion the cursor +at some line on this screen. + +>> Do this now, move the cursor and kill that line with C-k. + +Note that a single C-k kills the contents of the line, and a second +C-k kills the line itself, and make all the other lines move up. If +you give C-k a repeat count, it kills that many lines AND their +contents. + +The text that has just disappeared is saved so that you can +retrieve it. To retrieve the last killed text and put it where +the cursor currently is, type C-y. + +>> Try it; type C-y to yank the text back. + +Think of C-y as if you were yanking something back that someone +took away from you. Notice that if you do several C-k's in a row +the text that is killed is all saved together so that one C-y will +yank all of the lines. + +>> Do this now, type C-k several times. + +Now to retrieve that killed text: + +>> Type C-y. Then move the cursor down a few lines and type C-y + again. You now see how to copy some text. + + +FILES +----- + +In order to make the text you edit permanent, you must put it in a +file. Otherwise, it will go away when your invocation of Emacs goes +away. You put your editing in a file by "finding" the file. What +finding means is that you see the contents of the file in your Emacs; +and, loosely speaking, what you are editing is the file itself. +However, the changes still don't become permanent until you "save" the +file. This is so you can have control to avoid leaving a half-changed +file around when you don't want to. + +If you look near the bottom of the screen you will see a line that +begins and ends with dashes, and contains the string: + "Mg: TUTORIAL" +Your copy of the Emacs tutorial is called "TUTORIAL". Whatever +file you find, that file's name will appear in that precise +spot. + +The commands for finding and saving files are unlike the other +commands you have learned in that they consist of two characters. +They both start with the character Control-x. There is a whole series +of commands that start with Control-x; many of them have to do with +files, buffers, and related things, and all of them consist of +Control-x followed by some other character. + +Another thing about the command for finding a file is that you have +to say what file name you want. We say the command "reads an argument +from the terminal" (in this case, the argument is the name of the +file). After you type the command + + C-x C-f Find a file + +Emacs asks you to type the file name. It echoes on the bottom line of +the screen. When you type <Return> to end the file name it disappears. + +>> Type C-x C-f, then type C-g. This cancels the C-x C-f command + that was using the minibuffer. So you do not find any file. + +In a little while the file contents appear on the screen. You can +edit the contents. When you wish to make the changes permanent, +issue the command + + C-x C-s Save the file + +The contents of Emacs are written into the file. + +When saving is finished, Emacs prints the name of the file written. +You should save fairly often, so that you will not lose very much +work if the system should crash. + +>> Type C-x C-s, saving your copy of the tutorial. + This should print "Wrote TUTORIAL" at the bottom of the screen. + +To make a new file, just find it "as if" it already existed. Then +start typing in the text. When you ask to "save" the file, Emacs +will really create the file with the text that you have inserted. +>From then on, you can consider yourself to be editing an already +existing file. + + +BUFFERS +------- + +If you find a second file with C-x C-f, the first file remains inside +Emacs. This way you can get quite a number of files inside Emacs. + +The object inside Emacs which holds the text read from one file +is called a "buffer." Finding a file makes a new buffer inside Emacs. +To see a list of the buffers that exist in Emacs, type + + C-x C-b List buffers + +>> Try C-x C-b now. + +See how each buffer has a name, and it may also have a file name +for the file whose contents it holds. Some buffers do not correspond +to files. For example, the buffer named "*Buffer List*" does +not have any file. It is the buffer which contains the buffer +list that was made by C-x C-b. ANY text you see in an Emacs window +has to be in some buffer. + +>> Type C-x 1 to get rid of the buffer list. + +If you make changes to the text of one file, then find another file, +this does not save the first file. Its changes remain inside Emacs, +in that file's buffer. The creation or editing of the second file's +buffer has no effect on the first file's buffer. This is very useful, +but it also means that you need a convenient way to save the first +file's buffer. It would be a nuisance to have to switch back to +it with C-x C-f in order to save it with C-x C-s. So we have + + C-x s Save some buffers + +C-x s goes through the list of all the buffers you have +and finds the ones that contain files you have changed. +For each such buffer, C-x s asks you whether to save it. + + +EXTENDING THE COMMAND SET +------------------------- + +There are many, many more Emacs commands than could possibly be put +on all the control and meta characters. Emacs gets around this with +the X (eXtend) command. This comes in two flavors: + + C-x Character eXtend. Followed by one character. + M-x Named command eXtend. Followed by a long name. + +These are commands that are generally useful but used less than the +commands you have already learned about. You have already seen two +of them: the file commands C-x C-f to Find and C-x C-s to Save. +Another example is the command to tell Emacs that you'd like to stop +editing and get rid of Emacs. The command to do this is C-x C-c. +(Don't worry; it offers to save each changed file before it kills the +Emacs.) + +C-z is the usual way to exit Emacs, because it is always better not to +kill the Emacs if you are going to do any more editing. On systems +which allow it, C-z exits from Emacs to a CLI but does not destroy the +Emacs; you can resume editing by ending that CLI or depth arranging. + +You would use C-x C-c if you were running out of memory. You would +also use it to exit an Emacs invoked under mail handling programs and +other random utilities, since they may not believe you have really +finished using the Emacs if it continues to exist. + +There are many C-x commands. The ones you know are: + + C-x C-f Find file. + C-x C-s Save file. + C-x C-b List buffers. + C-x C-c Quit Emacs. + +Named eXtended commands are commands which are used even less +frequently, or commands which are used only in certain modes. These +commands are usually called "functions". An example is the function +replace-string, which globally replaces one string with another. When +you type M-x, Emacs prompts you at the bottom of the screen with +M-x and you should type the name of the function you wish to call; in +this case, "query-replace". Just type "que<TAB>" and Emacs will +complete the name. End the command name with <Return>. +Then type the two "arguments"--the string to be replaced, and the string +to replace it with--each one ended with a Return. + +>> Move the cursor to the blank line two lines below this one. + Then type M-x repl s<Return>changed<Return>altered<Return>. + + Notice how this line has changed: you've replaced + the word c-h-a-n-g-e-d with "altered" wherever it occured + after the cursor. + + +MODE LINE +--------- + +If Emacs sees that you are typing commands slowly it shows them to you +at the bottom of the screen in an area called the "echo area." The echo +area contains the bottom line of the screen. The line immediately above +it is called the MODE LINE. The mode line says something like + +--**-Mg: TUTORIAL (fundamental)------------------------ + +This is a very useful "information" line. + +The stars near the front mean that you have made changes to the text. +Right after you visit or save a file, there are no stars, just dashes. + +The part of the mode line inside the parentheses is to tell you what +modes you are in. The default mode is fundamental which is what you +are in now. It is an example of a "mode". There are several modes in +Emacs for editing different styles of text, such as indent, bsmap, +fill, etc. Each mode makes a few commands behave differently. + +One mode which is very useful, especially for editing English text, is +Auto Fill mode. When this mode is on, Emacs breaks the line in +between words automatically whenever the line gets too long. You can +turn this mode on by doing M-x auto-fill-mode<Return>. When the mode +is on, you can turn it off by doing M-x auto-fill-mode<Return>. + +>> Type M-x auto-fill-mode<Return> now. Then insert a line of "asdf " + over again until you see it divide into two lines. You must put in + spaces between them because Auto Fill breaks lines only at spaces. + +The margin is usually set at 70 characters, but you can change it +with the C-x f command. You should give the margin setting you want +as a numeric argument. + +>> Type C-x f with an argument of 20. (C-u 2 0 C-x f). + Then type in some text and see Emacs fill lines of 20 + characters with it. Then set the margin back to 70 using + C-x f again. + +If you make changes in the middle of a paragraph, Auto Fill mode +does not re-fill it for you. +To re-fill the paragraph, type M-q (Meta-q) with the cursor inside +that paragraph. + +>> Move the cursor into the previous paragraph and type M-q. + +SEARCHING +--------- + +Emacs can do searches for strings (these are groups of contiguous +characters or words) either forward through the file or backward +through it. To search for the string means that you are trying to +locate it somewhere in the file and have Emacs show you where the +occurrences of the string exist. This type of search is somewhat +different from what you may be familiar with. It is a search that is +performed as you type in the thing to search for. The command to +initiate a search is C-s for forward search, and C-r for reverse +search. BUT WAIT! Don't do them now. When you type C-s you'll +notice that the string "I-search" appears as a prompt in the echo +area. This tells you that Emacs is in what is called an incremental +search waiting for you to type the thing that you want to search for. +<ESC> terminates a search. + +>> Now type C-s to start a search. SLOWLY, one letter at a time, + type the word 'cursor', pausing after you type each + character to notice what happens to the cursor. +>> Type C-s to find the next occurrence of "cursor". +>> Now type <DEL> four times and see how the cursor moves. +>> Type <ESC> to terminate the search. + +Did you see what happened? Emacs, in an incremental search, tries to +go to the occurrence of the string that you've typed out so far. To go +to the next occurrence of 'cursor' just type C-s again. If no such +occurrence exists Emacs beeps and tells you that it is a failing +search. C-g would also terminate the search. + +If you are in the middle of an incremental search and type <DEL>, +you'll notice that the last character in the search string is erased +and the search backs up to the last place of the search. For +instance, suppose you currently have typed 'cu' and you see that your +cursor is at the first occurrence of 'cu'. If you now type <DEL>, +the 'u' on the search line is erased and you'll be repositioned in the +text to the occurrence of 'c' where the search took you before you +typed the 'u'. This provides a useful means for backing up while you +are searching. + +If you are in the middle of a search and happen to type a control +character (other than a C-s or C-r, which tell Emacs to search for the +next occurrence of the string), the search is terminated. + +The C-s starts a search that looks for any occurrence of the search +string AFTER the current cursor position. But what if you want to +search for something earlier in the text? To do this, type C-r for +Reverse search. Everything that applies to C-s applies to C-r except +that the direction of the search is reversed. + + +GETTING MORE HELP +----------------- + +In this tutorial we have tried to supply just enough information to +get you started using Emacs. There is so much available in Emacs that +it would be impossible to explain it all here. However, you may want +to learn more about Emacs since it has numerous desirable features +that you don't know about yet. + + +CONCLUSION +---------- + +Remember, to exit Emacs permanently use C-x C-c. To exit to a shell +temporarily, so that you can come back in, use C-z. + +This tutorial is meant to be understandable to all new users, so if +you found something unclear, don't sit and blame yourself - complain! + + +COPYING +------- + +This tutorial, like all of GNU Emacs, is copyrighted, and comes with +permission to distribute copies on certain conditions: + +Copyright (c) 1985 Richard M. Stallman + + Permission is granted to anyone to make or distribute verbatim copies + of this document as received, in any medium, provided that the + copyright notice and permission notice are preserved, + and that the distributor grants the recipient permission + for further redistribution as permitted by this notice. + + Permission is granted to distribute modified versions + of this document, or of portions of it, + under the above conditions, provided also that they + carry prominent notices stating who last altered them. + +The conditions for copying Emacs itself are slightly different +but in the same spirit. Please read the file COPYING and then +do give copies of GNU Emacs to your friends. +Help stamp out ownership of software by using, writing, +and sharing free software! + +Mg itself is public domain, and may be given away freely. See the +README file about differences from GNU emacs, and why Mg exists. + +******************************************************************************* +*** This document heavily cut by Randy M. Spencer to apply to *** +*** Mg written my Mike Meyer and gang. It was released *** +*** at the AAA users group meeting in Lafayette CA, an Amiga Users *** +*** Group. My profound thanks to Richard Stallman for his work, I *** +*** am proud to carry his initials. *** +*** Additional modifacations were done by Robert A. Larson for Mg *** +*** version 2a, mainly the name change from MicroGnuEmacs to Mg. *** +******************************************************************************* + +See other files accompanying this for more system specific information. diff --git a/usr.bin/mg/version.c b/usr.bin/mg/version.c new file mode 100644 index 00000000000..0f3a9e7562f --- /dev/null +++ b/usr.bin/mg/version.c @@ -0,0 +1,20 @@ +/* + * This file contains the string that get written + * out by the emacs-version command. + */ + +#define TRUE 1 /* include "def.h" when things get more complicated */ + +char version[] = "Mg 2a (formerly MicroGnuEmacs)"; + +/* + * Display the version. All this does + * is copy the version string onto the echo line. + */ +/*ARGSUSED*/ +showversion(f, n) +int f, n; +{ + ewprintf(version); + return TRUE; +} diff --git a/usr.bin/mg/window.c b/usr.bin/mg/window.c new file mode 100644 index 00000000000..25f6009ade6 --- /dev/null +++ b/usr.bin/mg/window.c @@ -0,0 +1,399 @@ +/* + * Window handling. + */ +#include "def.h" + +/* + * 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*/ +reposition(f, n) +{ +#ifndef GOSREC + curwp->w_force = (f & FFARG) ? (n>=0 ? n+1 : n) : 0; +#else + curwp->w_force = n; +#endif + curwp->w_flag |= WFFORCE; + 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. + * 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! + */ +/*ARGSUSED*/ +refresh(f, n) +{ + register WINDOW *wp; + register int oldnrow; + register int oldncol; + + oldnrow = nrow; + oldncol = ncol; + ttresize(); + if (nrow!=oldnrow || ncol!=oldncol) { + wp = wheadp; /* Find last. */ + while (wp->w_wndp != NULL) + wp = wp->w_wndp; + if (nrow < wp->w_toprow+3) { /* Check if too small. */ + ewprintf("Display unusable"); + return (FALSE); + } + wp->w_ntrows = nrow-wp->w_toprow-2; + sgarbf = TRUE; + update(); + ewprintf("New size %d by %d", nrow, ncol); + } 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*/ +nextwind(f, n) +{ + register WINDOW *wp; + + if ((wp=curwp->w_wndp) == NULL) + wp = wheadp; + curwp = wp; + curbp = wp->w_bufp; + return TRUE; +} + +#ifdef GOSMACS +/* not in Gnu Emacs */ +/* + * This command makes the previous + * window (previous => up the screen) the + * current window. There arn't any errors, + * although the command does not do a lot + * if there is 1 window. + */ +/*ARGSUSED*/ +prevwind(f, n) +{ + register WINDOW *wp1; + register WINDOW *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; +} +#endif + +/* + * 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 + * distruction of a window makes a buffer + * become undisplayed. + */ +/*ARGSUSED*/ +onlywind(f, n) +{ + register WINDOW *wp; + register LINE *lp; + register 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((char *) 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((char *) wp); + } + lp = curwp->w_linep; + i = curwp->w_toprow; + while (i!=0 && lback(lp)!=curbp->b_linep) { + --i; + lp = lback(lp); + } + curwp->w_toprow = 0; + curwp->w_ntrows = nrow-2; /* 2 = mode, echo. */ + curwp->w_linep = lp; + curwp->w_flag |= WFMODE|WFHARD; + 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. + */ +/*ARGSUSED*/ +splitwind(f, n) +{ + register WINDOW *wp; + register LINE *lp; + register int ntru; + register int ntrd; + int ntrl; + WINDOW *wp1, *wp2; + + if (curwp->w_ntrows < 3) { + ewprintf("Cannot split a %d line window", curwp->w_ntrows); + return (FALSE); + } + if ((wp = (WINDOW *)malloc(sizeof(WINDOW))) == NULL) { + ewprintf("Can't get %d", sizeof(WINDOW)); + return (FALSE); + } + ++curbp->b_nwnd; /* Displayed twice. */ + wp->w_bufp = curbp; + 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_flag = 0; + wp->w_force = 0; + ntru = (curwp->w_ntrows-1) / 2; /* Upper size */ + ntrl = (curwp->w_ntrows-1) - ntru; /* Lower size */ + lp = curwp->w_linep; + ntrd = 0; + while (lp != curwp->w_dotp) { + ++ntrd; + lp = lforw(lp); + } + lp = curwp->w_linep; + if (ntrd <= ntru) { /* Old is upper window. */ + if (ntrd == ntru) /* Hit mode line. */ + 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; + } else { /* Old is lower window */ + 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; + ++ntru; /* Mode line. */ + curwp->w_toprow += ntru; + curwp->w_ntrows = ntrl; + while (ntru--) + lp = lforw(lp); + } + curwp->w_linep = lp; /* Adjust the top lines */ + wp->w_linep = lp; /* if necessary. */ + curwp->w_flag |= WFMODE|WFHARD; + wp->w_flag |= WFMODE|WFHARD; + 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*/ +enlargewind(f, n) +{ + register WINDOW *adjwp; + register LINE *lp; + register 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; + } + if (curwp->w_wndp == adjwp) { /* Shrink below. */ + lp = adjwp->w_linep; + for (i=0; i<n && lp!=adjwp->w_bufp->b_linep; ++i) + lp = lforw(lp); + adjwp->w_linep = lp; + adjwp->w_toprow += n; + } else { /* Shrink above. */ + lp = curwp->w_linep; + for (i=0; i<n && lback(lp)!=curbp->b_linep; ++i) + lp = lback(lp); + curwp->w_linep = lp; + curwp->w_toprow -= n; + } + curwp->w_ntrows += n; + adjwp->w_ntrows -= n; + curwp->w_flag |= WFMODE|WFHARD; + adjwp->w_flag |= WFMODE|WFHARD; + 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. + */ +shrinkwind(f, n) +{ + register WINDOW *adjwp; + register LINE *lp; + register 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; + } + if (curwp->w_wndp == adjwp) { /* Grow below. */ + lp = adjwp->w_linep; + for (i=0; i<n && lback(lp)!=adjwp->w_bufp->b_linep; ++i) + lp = lback(lp); + adjwp->w_linep = lp; + adjwp->w_toprow -= n; + } else { /* Grow above. */ + lp = curwp->w_linep; + for (i=0; i<n && lp!=curbp->b_linep; ++i) + lp = lforw(lp); + curwp->w_linep = lp; + curwp->w_toprow += n; + } + curwp->w_ntrows -= n; + adjwp->w_ntrows += n; + curwp->w_flag |= WFMODE|WFHARD; + adjwp->w_flag |= WFMODE|WFHARD; + return (TRUE); +} + +/* + * Delete current window. Call shrink-window to do the screen + * updating, then throw away the window. + */ +/*ARGSUSED*/ +delwind(f, n) +{ + register WINDOW *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((char *) wp); + return TRUE; +} +/* + * Pick a window for a pop-up. + * Split the screen if there is only + * one window. Pick the uppermost window that + * isn't the current window. An LRU algorithm + * might be better. Return a pointer, or + * NULL on error. + */ +WINDOW * +wpopup() { + register WINDOW *wp; + + if (wheadp->w_wndp == NULL + && splitwind(FFRAND, 0) == FALSE) + return NULL; + wp = wheadp; /* Find window to use */ + while (wp!=NULL && wp==curwp) + wp = wp->w_wndp; + return wp; +} diff --git a/usr.bin/mg/word.c b/usr.bin/mg/word.c new file mode 100644 index 00000000000..e06b4cab2a8 --- /dev/null +++ b/usr.bin/mg/word.c @@ -0,0 +1,252 @@ +/* + * 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" + +/* + * Move the cursor backward by + * "n" words. All of the details of motion + * are performed by the "backchar" and "forwchar" + * routines. + */ +/*ARGSUSED*/ +backword(f, 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*/ +forwword(f, 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*/ +upperword(f, n) +{ + register int c; + + if (n < 0) return FALSE; + while (n--) { + while (inword() == FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + return TRUE; + } + 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(WFHARD); + } + 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*/ +lowerword(f, n) +{ + register int c; + + if (n < 0) return FALSE; + while (n--) { + while (inword() == FALSE) { + 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(WFHARD); + } + 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 and move past the end of the buffer. + */ +/*ARGSUSED*/ +capword(f, n) +{ + register int c; + VOID lchange(); + + if (n < 0) return FALSE; + while (n--) { + while (inword() == FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + return TRUE; + } + 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(WFHARD); + } + 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(WFHARD); + } + if (forwchar(FFRAND, 1) == FALSE) + return TRUE; + } + } + } + return TRUE; +} + +/* + * Kill forward by "n" words. + */ +/*ARGSUSED*/ +delfword(f, n) +{ + register RSIZE size; + register LINE *dotp; + register int doto; + + if (n < 0) + return FALSE; + if ((lastflag&CFKILL) == 0) /* Purge kill buffer. */ + kdelete(); + thisflag |= CFKILL; + dotp = curwp->w_dotp; + doto = curwp->w_doto; + size = 0; + while (n--) { + while (inword() == FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + goto out; /* Hit end of buffer. */ + ++size; + } + while (inword() != FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + goto out; /* Hit end of buffer. */ + ++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 wierd. Normally this is bound to "M-Rubout" and + * to "M-Backspace". + */ +/*ARGSUSED*/ +delbword(f, n) +{ + register RSIZE size; + VOID kdelete(); + + if (n < 0) return FALSE; + if ((lastflag&CFKILL) == 0) /* Purge kill buffer. */ + kdelete(); + thisflag |= CFKILL; + if (backchar(FFRAND, 1) == FALSE) + return (TRUE); /* Hit buffer start. */ + size = 1; /* One deleted. */ + while (n--) { + while (inword() == FALSE) { + if (backchar(FFRAND, 1) == FALSE) + goto out; /* Hit buffer start. */ + ++size; + } + while (inword() != FALSE) { + if (backchar(FFRAND, 1) == FALSE) + goto out; /* Hit buffer start. */ + ++size; + } + } + if (forwchar(FFRAND, 1) == FALSE) + return FALSE; + --size; /* Undo assumed delete. */ +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 setable. + */ +inword() { +/* 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]); +} + |