diff --git a/share/.gitignore b/share/.gitignore index 0d1783a..6847704 100644 --- a/share/.gitignore +++ b/share/.gitignore @@ -1,3 +1,4 @@ re.help misc unixbench +emg.keys diff --git a/src/cmd/Makefile b/src/cmd/Makefile index f3fdf44..f5c7ae2 100644 --- a/src/cmd/Makefile +++ b/src/cmd/Makefile @@ -11,8 +11,8 @@ CFLAGS += -Werror # Programs that live in subdirectories, and have makefiles of their own. # /bin SUBDIR = adb adc-demo ar as awk basic cc chflags chpass \ - cpp dc diff env fdisk find forth fstat glcdtest hostname \ - id la lcc lcpp ld ls login make man med \ + cpp dc diff emg env fdisk find forth fstat glcdtest \ + hostname id la lcc lcpp ld ls login make man med \ more nm passwd picoc portio printf pwm \ rdprof ranlib re renice retroforth scm setty sl \ sed sh smallc smlrc stty sysctl test uname wiznet xargs \ diff --git a/src/cmd/emg/.gitignore b/src/cmd/emg/.gitignore new file mode 100644 index 0000000..19a9d31 --- /dev/null +++ b/src/cmd/emg/.gitignore @@ -0,0 +1 @@ +emg diff --git a/src/cmd/emg/ChangeLog b/src/cmd/emg/ChangeLog new file mode 100644 index 0000000..9c41af6 --- /dev/null +++ b/src/cmd/emg/ChangeLog @@ -0,0 +1,41 @@ +ChangeLog +========= + +March 16, 2014 : emg 1.5 +------------------------ +Add line number to the mode line. +Implement prompted go to line function. +Remove lesser used expensive movement functions. +Documentation tweaks to reflect above changes. + +March 9, 2014 : emg 1.4 +----------------------- +Huge whitespace cleanup. +Make the window creation code mode consistent. +Small documentation fix. + +March 8, 2014 : emg 1.3 +----------------------- +Remove all OpenBSD support. emg is now for RetroBSD only. +Remove tmux alternative keybindings. +Revert Listbuffer command back to CTRL-x CTRL-b. + +December 2, 2013 : emg 1.2 +-------------------------- +Alternate keybindings for RetroBSD users using flow control terminal emulators. +Alternate keybindings for tmux users who are using the default control command. + +October 24, 2013 : emg 1.1 +-------------------------- +Listbuffer command is now CTRL-x l (originally CTRL-x CTRL-b). + This is because the default command keybinding of tmux is CTRL-b. +Search is now executed with instead of . + felt awkward, plus I don't search for newlines. +Lots of code cleanups (ttyio.c). + Removal of unused #if blocks. + Use panic() everywhere. + Fix all warnings from gcc -Wall. + +October 19, 2013 : emg 1.0 +-------------------------- +Initial version of emg. Current targets are OpenBSD and RetroBSD. diff --git a/src/cmd/emg/Makefile b/src/cmd/emg/Makefile new file mode 100644 index 0000000..ff0b3cc --- /dev/null +++ b/src/cmd/emg/Makefile @@ -0,0 +1,49 @@ +# emg Makefile +# for RetroBSD + +TOPSRC = $(shell cd ../../..; pwd) +include $(TOPSRC)/target.mk + +# Some basic CFLAGS. +CFLAGS = -Os -Wall -Werror + +# With the extra LDFLAGS, save some bytes. +CFLAGS += -ffunction-sections -fdata-sections + +# This reduces code size to 46K. +CFLAGS += -mips16 + +# Set the screen size. +# Will default to FORCE_COLS=80 and FORCE_ROWS=24 +# if not set here. +CFLAGS += -DFORCE_COLS=80 -DFORCE_ROWS=24 + +# with CFLAGS+= -ffunction-sections -fdatasections +LDFLAGS += -Wl,--gc-sections + +LIBS = -ltermcap -lc + +MAN = emg.0 +MANSRC = emg.1 + +OBJS = basic.o buffer.o display.o file.o fileio.o line.o main.o \ + random.o region.o search.o tcap.o ttyio.o window.o word.o + +all: emg ${MAN} + +emg: ${OBJS} + ${CC} ${LDFLAGS} -o emg.elf ${OBJS} ${LIBS} + ${OBJDUMP} -S emg.elf > emg.dis + ${SIZE} emg.elf + ${ELF2AOUT} emg.elf $@ && rm emg.elf + +${MAN}: ${MANSRC} + ${MANROFF} ${MANSRC} > ${MAN} + +install: all + install emg ${DESTDIR}/bin/emg + cp ${MAN} ${DESTDIR}/share/man/cat1/ + cp -p emg.keys ${DESTDIR}/share/emg.keys + +clean: + rm -f *.o *~ *.core *.bak *.dis emg ${MAN} diff --git a/src/cmd/emg/README b/src/cmd/emg/README new file mode 100644 index 0000000..1bde8f3 --- /dev/null +++ b/src/cmd/emg/README @@ -0,0 +1,44 @@ +emg +=== + +emg, or Ersatz Mg, is a very tiny Emacs-like text editor created by +combining elements of Ersatz Emacs and Mg (both the mg1a release and the +current OpenBSD-maintained version). + +The goal of this editor is to have something Emacs like for RetroBSD +(a release of 2.11BSD for PIC32 microcontrollers). After noticing that the +vi clone RetroBSD is using, VIrus, is GPL-licensed, I decided to provide +a better-licensed editor. I also decided that, as a vi user myself, it would +be easier to create an Emacs clone. Like you, I'm also unsure as to how that +conclusion was reached. + +I had initially tried to port Mg to RetroBSD but it was simply too large. +Ersatz Emacs does not build on RetroBSD, as RetroBSD is missing some functions +that Ersatz Emacs requires. It made sense to try to take from each and create +an editor that would work. + +In a way, emg has a double meaning: not only is it a combination of +the two programs that comprise it, it is also a substitute Mg after my initial +port failed. + +I have cleaned up some code where necessary; emg builds without errors on +RetroBSD. + +Patches are also very welcome. I ask that you keep in mind the resource +constraints of RetroBSD: everything must fit in 96K RAM. But of course, +smaller is better. + +I've left Chris Baird's Ersatz Emacs README here so others can better +appreciate the history of this software. + +As both Ersatz Emacs and Mg are Public Domain, emg is also Public Domain. + +Versions of emg up to and including 1.2 also supported OpenBSD; OpenBSD +has since dropped the older headers, such as sgtty.h and it is not worth +reimplementing these for OpenBSD, since OpenBSD maintains Mg. + +Tarballs can be found here: +http://devio.us/~bcallah/emg/ + +Github repo, for patches: +https://github.com/ibara/emg diff --git a/src/cmd/emg/README-ERSATZ b/src/cmd/emg/README-ERSATZ new file mode 100644 index 0000000..381b816 --- /dev/null +++ b/src/cmd/emg/README-ERSATZ @@ -0,0 +1,39 @@ +This shar file contains the source to a microemacs-derived text editor +that I have been personally hacking on for over a decade. + +Originally this was MicroEMACS 3.6 as released to mod.sources and the +Public Domain by Daniel Lawrence in 1986, and was itself based on the +work of Steve Wilhite and George Jones to MicroEMACS 2.0 (then also +public domain) by Dave Conroy. I would like to reiterate Lawrence's +thanks to them for writing such nice, well structured and documented +code. + +"Ersatz-Emacs", as I call it today, is the above text editor throughly +cleansed of routines and features that I personally never use. It is +also an editor MINIX-creator Andy Tanenbaum could describe as "fitting +inside a student's brain" (namely, mine). + +This source code should compile cleanly on any "modern" UN*X system +with a termcap/curses library. This release has been tested with +NetBSD and various Linux systems, although in the past when it was +still mostly MicroEMACS, proto-Ersatz-Emacs was an editor of choice on +SunOS, Solaris, Xenix, Minix/i386, and AIX. Supporting these and +similar systems should not be difficult. + +I encourage people to personalise this very simple editor to their own +requirements. Please send any useful bug reports and fixes back to me, +but I'm not really interested in incorporating new features unless it +simplifies the program further. Feel free to do a code-fork and +distribute your own perfect text editor. + +The title "Ersatz" comes from the category Richard Stallman uses in +MIT AI Memo 519a to describe those editors that are a surface-deep +imitation (key bindings) of "real" ITS Emacs. If you are familiar with +any Emacs-variant editor, you should have few problems with Ersatz. + +All source code of this program is in the Public Domain. I am a rabid +Stallmanite weenie, but it would be improper to publish this under a +different licence than it was given to me with. + +-- +Chris Baird,, diff --git a/src/cmd/emg/basic.c b/src/cmd/emg/basic.c new file mode 100644 index 0000000..ca7d30f --- /dev/null +++ b/src/cmd/emg/basic.c @@ -0,0 +1,266 @@ +/* This file is in the public domain. */ + +/* + * The routines in this file move the cursor around on the screen. They + * compute a new value for the cursor, then adjust ".". The display code + * always updates the cursor location, so only moves between lines, or + * functions that adjust the top line in the window and invalidate the + * framing, are hard. + */ + +#include /* atoi(3), ugh */ +#include "estruct.h" +#include "edef.h" + +extern int getccol(int bflg); +extern int inword(); +extern void mlwrite(); +extern int mlreplyt(); + +int forwchar(int f, int n); +int backchar(int f, int n); +int forwline(int f, int n); +int backline(int f, int n); + +/* + * This routine, given a pointer to a LINE, and the current cursor goal + * column, return the best choice for the offset. The offset is returned. + * Used by "C-N" and "C-P". + */ +long getgoal(LINE *dlp) +{ + int col = 0; + int dbo = 0; + int newcol, c; + + while (dbo != llength(dlp)) + { + c = lgetc(dlp, dbo); + newcol = col; + if (c == '\t') + newcol |= 0x07; + else if (c < 0x20 || c == 0x7F) + ++newcol; + ++newcol; + if (newcol > curgoal) + break; + col = newcol; + ++dbo; + } + return (dbo); +} + +/* + * Move the cursor to the + * beginning of the current line. + * Trivial. + */ +/* ARGSUSED0 */ +int gotobol(int f, int n) +{ + curwp->w_doto = 0; + return (TRUE); +} + +/* + * Move the cursor to the end of the current line. Trivial. No errors. + */ +/* ARGSUSED0 */ +int gotoeol(int f, int n) +{ + curwp->w_doto = llength(curwp->w_dotp); + return (TRUE); +} + +/* + * Move the cursor backwards by "n" characters. If "n" is less than zero call + * "forwchar" to actually do the move. Otherwise compute the new cursor + * location. Error if you try and move out of the buffer. Set the flag if the + * line pointer for dot changes. + */ +int backchar(int f, int n) +{ + 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) + return (FALSE); + curwp->w_dotp = lp; + curwp->w_doto = llength(lp); + curwp->w_flag |= WFMOVE; + curwp->w_dotline--; + } + else + curwp->w_doto--; + } + return (TRUE); +} + +/* + * Move the cursor forwards by "n" characters. If "n" is less than zero call + * "backchar" to actually do the move. Otherwise compute the new cursor + * location, and move ".". Error if you try and move off the end of the + * buffer. Set the flag if the line pointer for dot changes. + */ +int forwchar(int f, int n) +{ + if (n < 0) + return (backchar(f, -n)); + while (n--) + { + if (curwp->w_doto == llength(curwp->w_dotp)) + { + if (curwp->w_dotp == curbp->b_linep) + return (FALSE); + curwp->w_dotp = lforw(curwp->w_dotp); + curwp->w_doto = 0; + curwp->w_flag |= WFMOVE; + curwp->w_dotline++; + } + else + curwp->w_doto++; + } + return (TRUE); +} + +/* + * move to a particular line. argument (n) must be a positive integer for this + * to actually do anything + */ +int gotoline(int f, int n) +{ + if ((n < 1) || (n > curwp->w_bufp->b_lines)) /* if a bogus argument...then leave */ + return (FALSE); /* but we should never get here */ + + /* first, we go to the start of the buffer */ + curwp->w_dotp = lforw(curbp->b_linep); + curwp->w_doto = 0; + curwp->w_dotline = 0; /* and reset the line number */ + return (forwline(f, n - 1)); +} + +/* + * Prompt for which line number we want to go to, then execute gotoline() + * with that number as its argument. + * + * Make sure the bounds are within the file. + * + * Bound to M-G + */ +int setline(int f, int n) +{ + char setl[6]; + int l; + + (void)mlreplyt("Go to line: ", setl, 6, 10); + l = atoi(setl); /* XXX: This sucks! */ + + if (l < 1) + l = 1; + else if (l > curwp->w_bufp->b_lines) + l = curwp->w_bufp->b_lines; + + gotoline(f, l); + return (TRUE); +} + +/* + * Goto the beginning of the buffer. Massive adjustment of dot. This is + * considered to be hard motion; it really isn't if the original value of dot + * is the same as the new value of dot. Normally bound to "M-<". + */ +/* ARGSUSED0 */ +int gotobob(int f, int n) +{ + curwp->w_dotp = lforw(curbp->b_linep); + curwp->w_doto = 0; + curwp->w_flag |= WFHARD; + curwp->w_dotline = 0; + return (TRUE); +} + +/* + * Move to the end of the buffer. Dot is always put at the end of the file + * (ZJ). The standard screen code does most of the hard parts of update. + * Bound to "M->". + */ +/* ARGSUSED0 */ +int gotoeob(int f, int n) +{ + curwp->w_dotp = curbp->b_linep; + curwp->w_doto = 0; + curwp->w_flag |= WFHARD; + curwp->w_dotline = curwp->w_bufp->b_lines; + 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. Bound to "C-N". No errors are possible. + */ +int forwline(int f, int n) +{ + LINE *dlp; + + if (n < 0) + return (backline(f, -n)); + if ((lastflag & CFCPCN) == 0)/* Reset goal if last */ + curgoal = getccol(FALSE); /* not C-P or C-N */ + thisflag |= CFCPCN; + dlp = curwp->w_dotp; + while (n-- && dlp != curbp->b_linep) + { + dlp = lforw(dlp); + curwp->w_dotline++; + } + curwp->w_dotp = dlp; + curwp->w_doto = getgoal(dlp); + curwp->w_flag |= WFMOVE; + 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. No errors are possible. Bound to "C-P". + */ +int backline(int f, int n) +{ + LINE *dlp; + + if (n < 0) + return (forwline(f, -n)); + if ((lastflag & CFCPCN) == 0)/* Reset goal if the */ + curgoal = getccol(FALSE); /* last isn't C-P, C-N */ + thisflag |= CFCPCN; + dlp = curwp->w_dotp; + while (n-- && lback(dlp) != curbp->b_linep) + { + dlp = lback(dlp); + curwp->w_dotline--; + } + curwp->w_dotp = dlp; + curwp->w_doto = getgoal(dlp); + curwp->w_flag |= WFMOVE; + return (TRUE); +} + +/* + * Set the mark in the current window to the value of "." in the window. No + * errors are possible. Bound to "M-.". + */ +/* ARGSUSED0 */ +int setmark(int f, int n) +{ + curwp->w_markp = curwp->w_dotp; + curwp->w_marko = curwp->w_doto; + mlwrite("[Mark set]"); + return (TRUE); +} diff --git a/src/cmd/emg/buffer.c b/src/cmd/emg/buffer.c new file mode 100644 index 0000000..ac49d40 --- /dev/null +++ b/src/cmd/emg/buffer.c @@ -0,0 +1,494 @@ +/* This file is in the public domain. */ + +/* + * Buffer management. Some of the functions are internal, and some are actually + * attached to user keys. Like everyone else, they set hints for the display + * system. + */ + +#include /* free(3), malloc(3) */ +#include /* strncpy(3) */ +#include "estruct.h" +#include "edef.h" + +extern int mlreply(char *prompt, char *buf, int nbuf); +extern int readin(char fname[]); +extern void mlwrite(); +extern void mlerase(); +extern int mlyesno(char *prompt); +extern void lfree(LINE *lp); +extern WINDOW *wpopup(); +extern LINE *lalloc(); + +int swbuffer(BUFFER *bp); +int usebuffer(int f, int n); +int nextbuffer(int f, int n); +int killbuffer(int f, int n); +int zotbuf(BUFFER *bp); +int namebuffer(int f, int n); +int listbuffers(int f, int n); +int makelist(); +void itoa(char buf[], int width, int num); +int addline(char *text); +int anycb(); +BUFFER* bfind(char *bname, int cflag, int bflag); +int bclear(BUFFER *bp); + +/* + * make buffer BP current + */ +int swbuffer(BUFFER *bp) +{ + WINDOW *wp; + + if (--curbp->b_nwnd == 0) + { /* Last use. */ + curbp->b_dotp = curwp->w_dotp; + curbp->b_doto = curwp->w_doto; + curbp->b_markp = curwp->w_markp; + curbp->b_marko = curwp->w_marko; + } + curbp = bp; /* Switch. */ + if (curbp->b_active != TRUE) + { /* buffer not active yet */ + /* read it in and activate it */ + readin(curbp->b_fname); + curbp->b_dotp = lforw(curbp->b_linep); + curbp->b_doto = 0; + curbp->b_active = TRUE; + } + curwp->w_bufp = bp; + curwp->w_linep = bp->b_linep; /* For macros, ignored */ + curwp->w_flag |= WFMODE | WFFORCE | WFHARD; /* Quite nasty */ + if (bp->b_nwnd++ == 0) + { /* First use */ + curwp->w_dotp = bp->b_dotp; + curwp->w_doto = bp->b_doto; + curwp->w_markp = bp->b_markp; + curwp->w_marko = bp->b_marko; + return (TRUE); + } + wp = wheadp; /* Look for old */ + while (wp != 0) + { + if (wp != curwp && wp->w_bufp == bp) + { + curwp->w_dotp = wp->w_dotp; + curwp->w_doto = wp->w_doto; + curwp->w_markp = wp->w_markp; + curwp->w_marko = wp->w_marko; + break; + } + wp = wp->w_wndp; + } + return (TRUE); +} + +/* + * 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. + */ +/* ARGSUSED0 */ +int usebuffer(int f, int n) +{ + BUFFER *bp; + char bufn[NBUFN]; + int s; + + if ((s = mlreply("Use buffer: ", bufn, NBUFN)) != TRUE) + return (s); + if ((bp = bfind(bufn, TRUE, 0)) == NULL) + return (FALSE); + return (swbuffer(bp)); +} + +/* switch to the next buffer in the buffer list + */ +/* ARGSUSED0 */ +int nextbuffer(int f, int n) +{ + BUFFER *bp; + + bp = curbp->b_bufp; + /* cycle through the buffers to find an eligable one */ + while ((bp == NULL) || (bp->b_flag & BFTEMP)) + { + if (bp == NULL) + bp = bheadp; + else + bp = bp->b_bufp; + } + return (swbuffer(bp)); +} + +/* + * 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!). Get quite upset if the buffer is being + * displayed. Clear the buffer (ask if the buffer has been changed). Then free + * the header line and the buffer header. Bound to "C-X K". + */ +/* ARGSUSED0 */ +int killbuffer(int f, int n) +{ + BUFFER *bp; + char bufn[NBUFN]; + int s; + + if ((s = mlreply("Kill buffer: ", bufn, NBUFN)) != TRUE) + return (s); + if ((bp = bfind(bufn, FALSE, 0)) == NULL) /* Easy if unknown */ + return (TRUE); + return (zotbuf(bp)); +} + +/* kill the buffer pointed to by bp */ +int zotbuf(BUFFER *bp) +{ + BUFFER *bp1, *bp2; + int s; + + if (bp->b_nwnd != 0) + { /* Error if on screen */ + mlwrite("Buffer is being displayed"); + return (FALSE); + } + if ((s = bclear(bp)) != TRUE) /* Blow text away */ + return (s); + free (bp->b_linep); /* Release header line */ + bp1 = 0; /* Find the header */ + bp2 = bheadp; + while (bp2 != bp) + { + bp1 = bp2; + bp2 = bp2->b_bufp; + } + bp2 = bp2->b_bufp; /* Next one in chain */ + if (bp1 == NULL) /* Unlink it */ + bheadp = bp2; + else + bp1->b_bufp = bp2; + free(bp); /* Release buffer block */ + return (TRUE); +} + +/* Rename the current buffer */ +/* ARGSUSED0 */ +int namebuffer(int f, int n) +{ + BUFFER *bp; /* pointer to scan through all buffers */ + char bufn[NBUFN]; /* buffer to hold buffer name */ + + /* prompt for and get the new buffer name */ + ask: + if (mlreply("Change buffer name to: ", bufn, NBUFN) != TRUE) + return (FALSE); + + /* and check for duplicates */ + bp = bheadp; + while (bp != 0) + { + if (bp != curbp) + { + /* if the names the same */ + if (strcmp(bufn, bp->b_bname) == 0) + goto ask; /* try again */ + } + bp = bp->b_bufp; /* onward */ + } + + strncpy(curbp->b_bname, bufn, NBUFN); /* copy buffer name to structure */ + curwp->w_flag |= WFMODE; /* make mode line replot */ + mlerase(); + return (TRUE); +} + +/* + * List all of the active buffers. First update the special buffer that holds + * the list. Next make sure at least 1 window is displaying the buffer list, + * splitting the screen if this is what it takes. Lastly, repaint all of the + * windows that are displaying the list. Bound to "C-X C-B". + */ +/* ARGSUSED0 */ +int listbuffers(int f, int n) +{ + WINDOW *wp; + BUFFER *bp; + int s; + + if ((s = makelist()) != TRUE) + return (s); + if (blistp->b_nwnd == 0) + { /* Not on screen yet */ + if ((wp = wpopup()) == NULL) + return (FALSE); + bp = wp->w_bufp; + if (--bp->b_nwnd == 0) + { + bp->b_dotp = wp->w_dotp; + bp->b_doto = wp->w_doto; + bp->b_markp = wp->w_markp; + bp->b_marko = wp->w_marko; + } + wp->w_bufp = blistp; + ++blistp->b_nwnd; + } + wp = wheadp; + while (wp != 0) + { + if (wp->w_bufp == blistp) + { + wp->w_linep = lforw(blistp->b_linep); + wp->w_dotp = lforw(blistp->b_linep); + wp->w_doto = 0; + wp->w_markp = 0; + wp->w_marko = 0; + wp->w_flag |= WFMODE | WFHARD; + } + wp = wp->w_wndp; + } + return (TRUE); +} + +/* + * This routine rebuilds the text in the special secret buffer that holds the + * buffer list. It is called by the list buffers command. Return TRUE if + * everything works. Return FALSE if there is an error (if there is no + * memory). + */ +int makelist() +{ + BUFFER *bp; + LINE *lp; + char *cp1, *cp2; + char b[7], line[128]; + int nbytes, s, c; + + blistp->b_flag &= ~BFCHG; /* Don't complain! */ + if ((s = bclear(blistp)) != TRUE) /* Blow old text away */ + return (s); + strncpy (blistp->b_fname, "", 1); + if (addline("AC Size Buffer File") == FALSE || + addline("-- ------- ------ ----") == FALSE) + return (FALSE); + bp = bheadp; + + /* build line to report global mode settings */ + cp1 = &line[0]; + *cp1++ = ' '; + *cp1++ = ' '; + *cp1++ = ' '; + + /* output the list of buffers */ + while (bp != 0) + { + if ((bp->b_flag & BFTEMP) != 0) + { /* Skip magic ones */ + bp = bp->b_bufp; + continue; + } + cp1 = &line[0]; /* Start at left edge */ + + /* output status of ACTIVE flag (has the file been read in? */ + if (bp->b_active == TRUE) /* "@" if activated */ + *cp1++ = '@'; + else + *cp1++ = ' '; + + /* output status of changed flag */ + if ((bp->b_flag & BFCHG) != 0) /* "*" if changed */ + *cp1++ = '*'; + else + *cp1++ = ' '; + *cp1++ = ' '; /* Gap */ + + nbytes = 0; /* Count bytes in buf */ + lp = lforw(bp->b_linep); + while (lp != bp->b_linep) + { + nbytes += llength(lp) + 1; + lp = lforw(lp); + } + itoa(b, 6, nbytes); /* 6 digit buffer size */ + cp2 = &b[0]; + while ((c = *cp2++) != 0) + *cp1++ = (char)c; + *cp1++ = ' '; /* Gap */ + cp2 = &bp->b_bname[0]; /* Buffer name */ + while ((c = *cp2++) != 0) + *cp1++ = (char)c; + cp2 = &bp->b_fname[0]; /* File name */ + if (*cp2 != 0) + { + while (cp1 < &line[2 + 1 + 5 + 1 + 6 + 1 + NBUFN]) /* XXX ??? */ + *cp1++ = ' '; + while ((c = *cp2++) != 0) + { + if (cp1 < &line[128 - 1]) + *cp1++ = (char)c; + } + } + *cp1 = 0; /* Add to the buffer */ + if (addline(line) == FALSE) + return (FALSE); + bp = bp->b_bufp; + } + return (TRUE); /* All done */ +} + +void itoa(char buf[], int width, int num) +{ + buf[width] = 0; /* End of string */ + while (num >= 10) + { /* Conditional digits */ + buf[--width] = (char)((num % 10) + '0'); + num /= 10; + } + buf[--width] = (char)(num + '0'); /* Always 1 digit */ + while (width != 0) /* Pad with blanks */ + buf[--width] = ' '; +} + +/* + * The argument "text" points to a string. Append this line to the buffer list + * buffer. Handcraft the EOL on the end. Return TRUE if it worked and FALSE if + * you ran out of room. + */ +int addline(char *text) +{ + LINE *lp; + int ntext, i; + + ntext = strlen(text); + if ((lp = lalloc(ntext)) == NULL) + return (FALSE); + for (i = 0; i < ntext; ++i) + lputc(lp, i, text[i]); + blistp->b_linep->l_bp->l_fp = lp; /* Hook onto the end */ + lp->l_bp = blistp->b_linep->l_bp; + blistp->b_linep->l_bp = lp; + lp->l_fp = blistp->b_linep; + if (blistp->b_dotp == blistp->b_linep) /* If "." is at the end */ + blistp->b_dotp = lp; /* move it to new line */ + return (TRUE); +} + +/* + * Look through the list of buffers. Return TRUE if there are any changed + * buffers. Buffers that hold magic internal stuff are not considered; who + * cares if the list of buffer names is hacked. Return FALSE if no buffers + * have been changed. + */ +int anycb() +{ + BUFFER *bp; + + bp = bheadp; + while (bp != NULL) + { + if ((bp->b_flag & BFTEMP) == 0 && (bp->b_flag & BFCHG) != 0) + return (TRUE); + bp = bp->b_bufp; + } + return (FALSE); +} + +/* + * Find a buffer, by name. Return a pointer to the BUFFER structure associated + * with it. If the named buffer is found, but is a TEMP buffer (like the + * buffer list) conplain. If the buffer is not found and the "cflag" is TRUE, + * create it. The "bflag" is the settings for the flags in in buffer. + */ +BUFFER* bfind(char *bname, int cflag, int bflag) +{ + BUFFER *bp, *sb; + LINE *lp; + + bp = bheadp; + while (bp != 0) + { + if (strcmp(bname, bp->b_bname) == 0) + { + if ((bp->b_flag & BFTEMP) != 0) + { + mlwrite ("Cannot select builtin buffer"); + return (0); + } + return (bp); + } + bp = bp->b_bufp; + } + if (cflag != FALSE) + { + if ((bp = (BUFFER *) malloc(sizeof(BUFFER))) == NULL) + return (0); + if ((lp = lalloc(0)) == NULL) + { + free(bp); + return (BUFFER*)0; + } + /* find the place in the list to insert this buffer */ + if (bheadp == NULL || strcmp(bheadp->b_bname, bname) > 0) + { + /* insert at the begining */ + bp->b_bufp = bheadp; + bheadp = bp; + } + else + { + sb = bheadp; + while (sb->b_bufp != 0) + { + if (strcmp(sb->b_bufp->b_bname, bname) > 0) + break; + sb = sb->b_bufp; + } + + /* and insert it */ + bp->b_bufp = sb->b_bufp; + sb->b_bufp = bp; + } + + /* and set up the other buffer fields */ + bp->b_active = TRUE; + bp->b_dotp = lp; + bp->b_doto = 0; + bp->b_markp = 0; + bp->b_marko = 0; + bp->b_flag = (char)bflag; + bp->b_nwnd = 0; + bp->b_linep = lp; + bp->b_lines = 1; + strncpy(bp->b_fname, "", 1); + strncpy(bp->b_bname, bname, NBUFN); + lp->l_fp = lp; + lp->l_bp = lp; + } + return (bp); +} + +/* + * This routine blows away all of the text in a buffer. If the buffer is + * marked as changed then we ask if it is ok to blow it away; this is to save + * the user the grief of losing text. The window chain is nearly always wrong + * if this gets called; the caller must arrange for the updates that are + * required. Return TRUE if everything looks good. + */ +int bclear(BUFFER *bp) +{ + LINE *lp; + int s; + + if ((bp->b_flag & BFTEMP) == 0 /* Not scratch buffer */ + && (bp->b_flag & BFCHG) != 0 /* Something changed */ + && (s = mlyesno("Discard changes")) != 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 = 0; /* Invalidate "mark" */ + bp->b_marko = 0; + bp->b_lines = 1; + return (TRUE); +} diff --git a/src/cmd/emg/display.c b/src/cmd/emg/display.c new file mode 100644 index 0000000..721425d --- /dev/null +++ b/src/cmd/emg/display.c @@ -0,0 +1,1015 @@ +/* This file is in the public domain. */ + +/* + * The functions in this file handle redisplay. There are two halves, the ones + * that update the virtual display screen, and the ones that make the physical + * display screen the same as the virtual display screen. These functions use + * hints that are left in the windows by the commands + */ + +#include +#include +#include +#include "estruct.h" +#include "edef.h" + +extern int typeahead(); +extern int ctrlg(); +extern int getccol(); + +void movecursor(int row, int col); +void mlerase(); +void vtinit(); +void vttidy(); +void vtmove(int row, int col); +void vtputc(int c); +void vtpute(int c); +int vtputs(const char *s); +void vteeol(); +void update(); +void updext(); +void updateline(int row, char vline[], char pline[], short *flags); +void modeline(WINDOW *wp); +void upmode(); +int mlyesno(char *prompt); +int mlreplyt(char *prompt, char *buf, int nbuf, char eolchar); +int mlreply(char *prompt, char *buf, int nbuf); +void mlwrite(); +void mlputs(char *s); +void mlputi(int i, int r); +void mlputli(long l, int r); + +typedef struct VIDEO { + short v_flag; /* Flags */ + char v_text[1]; /* Screen data */ +} VIDEO; + +#define VFCHG 0x0001 /* Changed flag */ +#define VFEXT 0x0002 /* extended (beyond column 80) */ +#define VFREV 0x0004 /* reverse video status */ +#define VFREQ 0x0008 /* reverse video request */ + +int vtrow = 0; /* Row location of SW cursor */ +int vtcol = 0; /* Column location of SW cursor */ +int ttrow = HUGE; /* Row location of HW cursor */ +int ttcol = HUGE; /* Column location of HW cursor */ +int lbound = 0; /* leftmost column of line being displayed */ + +VIDEO **vscreen; /* Virtual screen */ +VIDEO **pscreen; /* Physical screen */ + +/* + * Send a command to the terminal to move the hardware cursor to row "row" and + * column "col". The row and column arguments are origin 0. Optimize out + * random calls. Update "ttrow" and "ttcol". + */ +void movecursor(int row, int col) +{ + if (row != ttrow || col != ttcol) + { + ttrow = row; + ttcol = col; + (*term.t_move) (row, col); + } +} + +/* + * Erase the message line. This is a special routine because the message line + * is not considered to be part of the virtual screen. It always works + * immediately; the terminal buffer is flushed via a call to the flusher. + */ +void mlerase() +{ + int i; + + movecursor(term.t_nrow, 0); + if (eolexist == TRUE) + (*term.t_eeol) (); + else + { + for (i = 0; i < term.t_ncol - 1; i++) + (*term.t_putchar) (' '); + movecursor(term.t_nrow, 1); /* force the move! */ + movecursor(term.t_nrow, 0); + } + (*term.t_flush) (); + mpresf = FALSE; +} + +/* + * 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. All the other things get initialized at compile time. + * The original window has "WFCHG" set, so that it will get completely redrawn + * on the first call to "update". + */ +void vtinit() +{ + int i; + VIDEO *vp; + + (*term.t_open) (); + (*term.t_rev) (FALSE); + vscreen = (VIDEO **) malloc(term.t_nrow * sizeof(VIDEO *)); + + if (vscreen == NULL) + exit (1); + + pscreen = (VIDEO **) malloc(term.t_nrow * sizeof(VIDEO *)); + + if (pscreen == NULL) + exit (1); + + for (i = 0; i < term.t_nrow; ++i) + { + vp = (VIDEO *) malloc(sizeof(VIDEO) + term.t_ncol); + + if (vp == NULL) + exit (1); + + vp->v_flag = 0; + vscreen[i] = vp; + vp = (VIDEO *) malloc(sizeof(VIDEO) + term.t_ncol); + + if (vp == NULL) + exit (1); + + vp->v_flag = 0; + pscreen[i] = vp; + } +} + +/* + * Clean up the virtual terminal system, in anticipation for a return to the + * operating system. Move down to the last line and clear it out (the next + * system prompt will be written in the line). Shut down the channel to the + * terminal. + */ +void vttidy() +{ + mlerase(); + movecursor(term.t_nrow, 0); + (*term.t_close) (); +} + +/* + * Set the virtual cursor to the specified row and column on the virtual + * screen. There is no checking for nonsense values; this might be a good idea + * during the early stages. + */ +void vtmove(int row, int col) +{ + vtrow = row; + vtcol = col; +} + +/* + * Write a character to the virtual screen. The virtual row and column are + * updated. If the line is too long put a "$" in the last column. This routine + * only puts printing characters into the virtual terminal buffers. Only + * column overflow is checked. + */ +void vtputc(int c) +{ + VIDEO *vp; + + vp = vscreen[vtrow]; + + if (vtcol >= term.t_ncol) + { + vtcol = (vtcol + 0x07) & ~0x07; + vp->v_text[term.t_ncol - 1] = '$'; + } + else if (c == '\t') + { + do + { + vtputc(' '); + } + while ((vtcol & 0x07) != 0); + } + else if (c < 0x20 || c == 0x7F) + { + vtputc('^'); + vtputc(c ^ 0x40); + } + 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(int c) +{ + VIDEO *vp; + + vp = vscreen[vtrow]; + + if (vtcol >= term.t_ncol) + { + vtcol = (vtcol + 0x07) & ~0x07; + vp->v_text[term.t_ncol - 1] = '$'; + } + else if (c == '\t') + { + do + { + vtpute(' '); + } + while (((vtcol + lbound) & 0x07) != 0); + } + else if (c < 0x20 || c == 0x7F) + { + vtpute('^'); + vtpute(c ^ 0x40); + } + else + { + if (vtcol >= 0) + vp->v_text[vtcol] = c; + ++vtcol; + } +} + +/* + * Output a string to the mode line, report how long it was. + * From OpenBSD mg. + */ +int vtputs(const char *s) +{ + int n = 0; + + while (*s != '\0') { + vtputc(*s++); + ++n; + } + return (n); +} + +/* + * [In the virtual screen] Erase from the end of the software cursor to the + * end of the line on which the software cursor is located. + */ +void vteeol() +{ + VIDEO *vp; + + vp = vscreen[vtrow]; + while (vtcol < term.t_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() +{ + VIDEO *vp1, *vp2; + LINE *lp; + WINDOW *wp; + int i, j, c; + + if (typeahead()) + return; + + /* update the reverse video flags for any mode lines out there */ + for (i = 0; i < term.t_nrow; ++i) + vscreen[i]->v_flag &= ~VFREQ; + + wp = wheadp; + while (wp != NULL) + { + vscreen[wp->w_toprow + wp->w_ntrows]->v_flag |= VFREQ; + wp = wp->w_wndp; + } + + wp = wheadp; + while (wp != NULL) + { + /* Look at any window with update flags set on */ + if (wp->w_flag != 0) + { + /* If not force reframe, check the framing */ + 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); + } + } + /* Not acceptable, better compute a new value for the line at the top + * of the window. Then set the "WFHARD" flag to force full redraw */ + i = wp->w_force; + 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: + /* Try to use reduced update. Mode line update has its own special + * flag. The fast update is used if the only thing to do is within + * the line editing */ + lp = wp->w_linep; + i = wp->w_toprow; + if ((wp->w_flag & ~WFMODE) == WFEDIT) + { + while (lp != wp->w_dotp) + { + ++i; + lp = lforw(lp); + } + vscreen[i]->v_flag |= VFCHG; + vtmove(i, 0); + for (j = 0; j < llength(lp); ++j) + vtputc(lgetc(lp, j)); + vteeol(); + } + else if ((wp->w_flag & (WFEDIT | WFHARD)) != 0) + { + while (i < wp->w_toprow + wp->w_ntrows) + { + vscreen[i]->v_flag |= VFCHG; + vtmove(i, 0); + /* if line has been changed */ + if (lp != wp->w_bufp->b_linep) + { + for (j = 0; j < llength(lp); ++j) + vtputc(lgetc(lp, j)); + lp = lforw(lp); + } + vteeol(); + ++i; + } + } + } + modeline(wp); /* always update the modeline so line number is correct */ + wp->w_flag = 0; + wp->w_force = 0; + + wp = wp->w_wndp; /* and onward to the next window */ + } + + /* Always recompute the row and column number of the hardware cursor. This + * is the only update for simple moves */ + lp = curwp->w_linep; + 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') + curcol |= 0x07; + else if (c < 0x20 || c == 0x7F) + ++curcol; + ++curcol; + } + if (curcol >= term.t_ncol - 1) + { /* extended line */ + /* flag we are extended and changed */ + vscreen[currow]->v_flag |= VFEXT | VFCHG; + updext(); /* 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 < term.t_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; + } + /* and onward to the next window */ + wp = wp->w_wndp; + } + + /* Special hacking if the screen is garbage. Clear the hardware screen, and + * update your copy to agree with it. Set all the virtual screen change + * bits, to force a full update */ + if (sgarbf != FALSE) + { + for (i = 0; i < term.t_nrow; ++i) + { + vscreen[i]->v_flag |= VFCHG; + vp1 = pscreen[i]; + for (j = 0; j < term.t_ncol; ++j) + vp1->v_text[j] = ' '; + } + + movecursor(0, 0); /* Erase the screen */ + (*term.t_eeop) (); + sgarbf = FALSE; /* Erase-page clears */ + mpresf = FALSE; /* the message area */ + } + /* Make sure that the physical and virtual displays agree. Unlike before, + * the "updateline" code is only called with a line that has been updated + * for sure */ + for (i = 0; i < term.t_nrow; ++i) + { + vp1 = vscreen[i]; + + /* for each line that needs to be updated, or that needs its reverse + * video status changed, call the line updater */ + j = vp1->v_flag; + if (((j & VFCHG) != 0) || (((j & VFREV) == 0) != ((j & VFREQ) == 0))) + { + if (typeahead()) + return; + vp2 = pscreen[i]; + updateline(i, &vp1->v_text[0], &vp2->v_text[0], &vp1->v_flag); + } + } + + /* Finally, update the hardware cursor and flush out buffers */ + movecursor(currow, curcol - lbound); + (*term.t_flush) (); +} + +/* 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() +{ + LINE *lp; /* pointer to current line */ + int rcursor; /* real cursor location */ + int j; /* index into line */ + + /* calculate what column the real cursor will end up in */ + rcursor = ((curcol - term.t_ncol) % term.t_scrsiz) + term.t_margin; + lbound = curcol - rcursor + 1; + + /* 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)); + /* truncate the virtual line */ + vteeol(); + /* and put a '$' in column 1 */ + vscreen[currow]->v_text[0] = '$'; +} + +/* + * Update a single line. This does not know how to use insert or delete + * character sequences; we are using VT52 functionality. Update the physical + * row and column variables. It does try an exploit erase to end of line. + */ +void updateline(int row, char vline[], char pline[], short *flags) +{ + char *cp1, *cp2, *cp3, *cp4, *cp5; + int nbflag; /* non-blanks to the right flag? */ + int rev; /* reverse video flag */ + int req; /* reverse video request flag */ + + /* set up pointers to virtual and physical lines */ + cp1 = &vline[0]; + cp2 = &pline[0]; + + /* if we need to change the reverse video status of the current line, we + * need to re-write the entire line */ + rev = *flags & VFREV; + req = *flags & VFREQ; + if (rev != req) + { + movecursor(row, 0); /* Go to start of line */ + (*term.t_rev) (req != FALSE); /* set rev video if needed */ + + /* scan through the line and dump it to the screen and the virtual + * screen array */ + cp3 = &vline[term.t_ncol]; + while (cp1 < cp3) + { + (*term.t_putchar) (*cp1); + ++ttcol; + *cp2++ = *cp1++; + } + (*term.t_rev) (FALSE); /* turn rev video off */ + + /* update the needed flags */ + *flags &= ~VFCHG; + if (req) + *flags |= VFREV; + else + *flags &= ~VFREV; + return; + } + + /* advance past any common chars at the left */ + while (cp1 != &vline[term.t_ncol] && cp1[0] == cp2[0]) + { + ++cp1; + ++cp2; + } + + /* This can still happen, even though we only call this routine on changed + * lines. A hard update is always done when a line splits, a massive change + * is done, or a buffer is displayed twice. This optimizes out most of the + * excess updating. A lot of computes are used, but these tend to be hard + * operations that do a lot of update, so I don't really care */ + /* if both lines are the same, no update needs to be done */ + if (cp1 == &vline[term.t_ncol]) + return; + + /* find out if there is a match on the right */ + nbflag = FALSE; + cp3 = &vline[term.t_ncol]; + cp4 = &pline[term.t_ncol]; + + while (cp3[-1] == cp4[-1]) + { + --cp3; + --cp4; + if (cp3[0] != ' ') /* Note if any nonblank */ + nbflag = TRUE; /* in right match */ + } + + cp5 = cp3; + + if (nbflag == FALSE && eolexist == TRUE) + { /* Erase to EOL ? */ + while (cp5 != cp1 && cp5[-1] == ' ') + --cp5; + if (cp3 - cp5 <= 3) /* Use only if erase is */ + cp5 = cp3; /* fewer characters */ + } + movecursor (row, cp1 - &vline[0]); /* Go to start of line */ + + while (cp1 != cp5) + { /* Ordinary */ + (*term.t_putchar) (*cp1); + ++ttcol; + *cp2++ = *cp1++; + } + + if (cp5 != cp3) + { /* Erase */ + (*term.t_eeol) (); + while (cp1 != cp3) + *cp2++ = *cp1++; + } + *flags &= ~VFCHG; /* flag this line is changed */ +} + +/* + * 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. + */ +void modeline(WINDOW *wp) +{ + BUFFER *bp; + int lchar; /* character to draw line in buffer with */ + int n; /* cursor position count */ + int len; /* line/column display check */ + char sl[25]; /* line/column display (probably overkill) */ + + n = wp->w_toprow + wp->w_ntrows; /* Location */ + vscreen[n]->v_flag |= VFCHG; /* Redraw next time */ + vtmove(n, 0); /* Seek to right line */ + if (wp == curwp) /* mark the current buffer */ + lchar = '='; + else + if (revexist) + lchar = ' '; + else + lchar = '-'; + + vtputc(lchar); + bp = wp->w_bufp; + + if ((bp->b_flag & BFCHG) != 0) /* "*" if changed */ + vtputc('*'); + else + vtputc(lchar); + + n = 2; + /* This is the version string. Do not forget to + * increment when releasing a new version. */ + n += vtputs(" emg 1.5 "); + + vtputc(lchar); + vtputc(lchar); + vtputc(' '); + n += 3; + + n += vtputs(&(bp->b_bname[0])); + + vtputc(' '); + vtputc(lchar); + vtputc(lchar); + n += 3; + + if (bp->b_fname[0] != 0) /* File name */ + { + vtputc(' '); + ++n; + n += vtputs("File: "); + + n += vtputs(&(bp->b_fname[0])); + + vtputc(' '); + vtputc(lchar); + vtputc(lchar); + n += 3; + } + + len = snprintf(sl, sizeof(sl), " %d%% (%d,%d) ", + ((wp->w_dotline +1) / bp->b_lines), + (wp->w_dotline + 1), getccol(FALSE)); + if (len < sizeof(sl) && len != -1) + n += vtputs(sl); + + while (n < term.t_ncol) /* Pad to full width */ + { + vtputc(lchar); + ++n; + } +} + +/* update all the mode lines */ +void upmode() +{ + WINDOW *wp; + + wp = wheadp; + while (wp != NULL) + { + wp->w_flag |= WFMODE; + wp = wp->w_wndp; + } +} + +/* + * Ask a yes or no question in the message line. Return either TRUE, FALSE, or + * ABORT. The ABORT status is returned if the user bumps out of the question + * with a ^G. Used any time a confirmation is required. + */ +int mlyesno(char *prompt) +{ + char c; /* input character */ + char buf[NPAT]; /* prompt to user */ + + for (;;) + { + /* build and prompt the user */ + strncpy(buf, prompt, 60); + + strncat(buf, " [y/n]? ", 9); + mlwrite(buf); + + /* get the responce */ + c = (*term.t_getchar) (); + if (c == BELL) /* Bail out! */ + return (ABORT); + if (c == 'y' || c == 'Y') + return (TRUE); + if (c == 'n' || c == 'N') + return (FALSE); + } +} + +/* A more generalized prompt/reply function allowing the caller to specify the + * proper terminator. If the terminator is not a return ('\n') it will echo as + * "" + */ +int mlreplyt(char *prompt, char *buf, int nbuf, char eolchar) +{ + int cpos, i, c; + + cpos = 0; + + if (kbdmop != NULL) + { + while ((c = *kbdmop++) != '\0') + buf[cpos++] = c; + buf[cpos] = 0; + if (buf[0] == 0) + return (FALSE); + return (TRUE); + } + mlwrite(prompt); + + for (;;) + { + /* get a character from the user. if it is a change it to a */ + c = (*term.t_getchar) (); + if (c == 0x0d) + c = '\n'; + + if (c == eolchar) + { + buf[cpos++] = 0; + + if (kbdmip != NULL) + { + if (kbdmip + cpos > &kbdm[NKBDM - 3]) + { + ctrlg(FALSE, 0); + (*term.t_flush) (); + return (ABORT); + } + for (i = 0; i < cpos; ++i) + *kbdmip++ = buf[i]; + } + (*term.t_putchar) ('\r'); + ttcol = 0; + (*term.t_flush) (); + + if (buf[0] == 0) + return (FALSE); + + return (TRUE); + + } + else if (c == 0x07) + { /* Bell, abort */ + (*term.t_putchar) ('^'); + (*term.t_putchar) ('G'); + ttcol += 2; + ctrlg (FALSE, 0); + (*term.t_flush) (); + return (ABORT); + } + else if (c == 0x7F || c == 0x08) + { /* rubout/erase */ + if (cpos != 0) + { + (*term.t_putchar) ('\b'); + (*term.t_putchar) (' '); + (*term.t_putchar) ('\b'); + --ttcol; + + if (buf[--cpos] < 0x20) + { + (*term.t_putchar) ('\b'); + (*term.t_putchar) (' '); + (*term.t_putchar) ('\b'); + --ttcol; + } + if (buf[cpos] == '\n') + { + (*term.t_putchar) ('\b'); + (*term.t_putchar) ('\b'); + (*term.t_putchar) (' '); + (*term.t_putchar) (' '); + (*term.t_putchar) ('\b'); + (*term.t_putchar) ('\b'); + --ttcol; + --ttcol; + } + (*term.t_flush) (); + } + } + else if (c == 0x15) + { /* C-U, kill */ + while (cpos != 0) + { + (*term.t_putchar) ('\b'); + (*term.t_putchar) (' '); + (*term.t_putchar) ('\b'); + --ttcol; + if (buf[--cpos] < 0x20) + { + (*term.t_putchar) ('\b'); + (*term.t_putchar) (' '); + (*term.t_putchar) ('\b'); + --ttcol; + } + } + (*term.t_flush) (); + } + else + { + if (cpos < nbuf - 1) + { + buf[cpos++] = c; + if ((c < ' ') && (c != '\n')) + { + (*term.t_putchar) ('^'); + ++ttcol; + c ^= 0x40; + } + if (c != '\n') + (*term.t_putchar) (c); + else + { /* put out for */ + (*term.t_putchar) ('<'); + (*term.t_putchar) ('N'); + (*term.t_putchar) ('L'); + (*term.t_putchar) ('>'); + ttcol += 3; + } + ++ttcol; + (*term.t_flush) (); + } + } + } +} + +/* + * Write a prompt into the message line, then read back a response. Keep track + * of the physical position of the cursor. If we are in a keyboard macro throw + * the prompt away, and return the remembered response. This lets macros run + * at full speed. The reply is always terminated by a carriage return. Handle + * erase, kill, and abort keys. + */ +int mlreply(char *prompt, char *buf, int nbuf) +{ + return(mlreplyt (prompt, buf, nbuf, '\n')); +} + +/* + * Write a message into the message line. Keep track of the physical cursor + * position. A small class of printf like format items is handled. Assumes the + * stack grows down; this assumption is made by the "++" in the argument scan + * loop. Set the "message line" flag TRUE. + */ +void mlwrite(char *fmt, int arg) +{ + int c; + char *ap; + + if (eolexist == FALSE) + { + mlerase(); + (*term.t_flush) (); + } + movecursor(term.t_nrow, 0); + ap = (char *) &arg; + while ((c = *fmt++) != 0) + { + if (c != '%') + { + (*term.t_putchar) (c); + ++ttcol; + } + else + { + c = *fmt++; + switch (c) + { + case 'd': + mlputi(*(int *) ap, 10); + ap += sizeof(int); + break; + + case 'o': + mlputi(*(int *) ap, 8); + ap += sizeof(int); + break; + + case 'x': + mlputi(*(int *) ap, 16); + ap += sizeof(int); + break; + + case 'D': + mlputli(*(long *) ap, 10); + ap += sizeof(long); + break; + + case 's': + mlputs(*(char **) &ap); + ap += sizeof(char *); + break; + + case 'c': + (*term.t_putchar) (*ap); + ++ttcol; + ap += sizeof(char *); + break; + + default: + (*term.t_putchar) (c); + ++ttcol; + } + } + } + if (eolexist == TRUE) + (*term.t_eeol) (); + (*term.t_flush) (); + mpresf = TRUE; +} + +/* + * Write out a string. Update the physical cursor position. This assumes that + * the characters in the string all have width "1"; if this is not the case + * things will get screwed up a little. + */ +void mlputs(char *s) +{ + int c; + + while ((c = *s++) != 0) + { + (*term.t_putchar) (c); + ++ttcol; + } +} + +/* + * Write out an integer, in the specified radix. Update the physical cursor + * position. This will not handle any negative numbers; maybe it should. + */ +void mlputi(int i, int r) +{ + int q; + static char hexdigits[] = "0123456789abcdef"; + + if (i < 0) + { + i = -i; + (*term.t_putchar) ('-'); + } + q = i / r; + + if (q != 0) + mlputi(q, r); + + (*term.t_putchar) (hexdigits[i % r]); + ++ttcol; +} + +/* + * do the same except as a long integer. + */ +void mlputli(long l, int r) +{ + long q; + + if (l < 0) + { + l = -l; + (*term.t_putchar) ('-'); + } + q = l / r; + + if (q != 0) + mlputli(q, r); + + (*term.t_putchar) ((int) (l % r) + '0'); + ++ttcol; +} + diff --git a/src/cmd/emg/ebind.h b/src/cmd/emg/ebind.h new file mode 100644 index 0000000..a047139 --- /dev/null +++ b/src/cmd/emg/ebind.h @@ -0,0 +1,75 @@ +/* This file is in the public domain. */ + +/* + * This table is *roughly* in ASCII order, left to right across the + * characters of the command. This expains the funny location of the + * control-X commands. + */ + +KEYTAB keytab[] = { + {CTRL | '@', setmark}, + {CTRL | 'A', gotobol}, + {CTRL | 'B', backchar}, + {CTRL | 'D', forwdel}, + {CTRL | 'E', gotoeol}, + {CTRL | 'F', forwchar}, + {CTRL | 'G', ctrlg}, + {CTRL | 'H', backdel}, + {CTRL | 'I', tab}, + {CTRL | 'K', killtext}, + {CTRL | 'L', refresh}, + {CTRL | 'M', newline}, + {CTRL | 'N', forwline}, + {CTRL | 'O', openline}, + {CTRL | 'P', backline}, + {CTRL | 'Q', quote}, + {CTRL | 'R', backsearch}, + {CTRL | 'S', forwsearch}, + {CTRL | 'T', twiddle}, + {CTRL | 'W', killregion}, + {CTRL | 'Y', yank}, + {CTLX | '(', ctlxlp}, + {CTLX | ')', ctlxrp}, + {CTLX | '1', onlywind}, + {CTLX | '2', splitwind}, + {CTLX | 'B', usebuffer}, + {CTLX | 'E', ctlxe}, + {CTLX | 'F', setfillcol}, + {CTLX | 'K', killbuffer}, + {CTLX | 'N', filename}, + {CTLX | 'O', nextwind}, + {CTLX | 'S', filesave}, /* non-standard */ + {CTLX | 'Q', quote}, /* non-standard */ + {CTLX | 'X', nextbuffer}, + {CTLX | '^', enlargewind}, + {CTLX | CTRL | 'B', listbuffers}, + {CTLX | CTRL | 'C', quit}, + {CTLX | CTRL | 'F', filefind}, + {CTLX | CTRL | 'I', insfile}, + {CTLX | CTRL | 'R', fileread}, + {CTLX | CTRL | 'S', filesave}, + {CTLX | CTRL | 'W', filewrite}, + {META | ' ', setmark}, + {META | '%', qreplace}, + {META | '.', setmark}, + {META | '<', gotobob}, + {META | '>', gotoeob}, + {META | 'B', backword}, + {META | 'C', capword}, + {META | 'D', delfword}, + {META | 'F', forwword}, + {META | 'G', setline}, /* non-standard */ + {META | 'L', lowerword}, + {META | 'R', sreplace}, + {META | 'S', forwsearch}, /* non-standard */ + {META | 'U', upperword}, + {META | 'W', copyregion}, + {META | 'Z', quickexit}, + {META | 0x7F, delbword}, + {META | CTRL | 'H', delbword}, + {META | CTRL | 'N', namebuffer}, + {0x7F, backdel}, + {META | '[', extendedcmd}, + {META | 'O', extendedcmd}, + {0, 0} +}; diff --git a/src/cmd/emg/edef.h b/src/cmd/emg/edef.h new file mode 100644 index 0000000..dd59b1e --- /dev/null +++ b/src/cmd/emg/edef.h @@ -0,0 +1,78 @@ +/* This file is in the public domain. */ + +#ifndef NULL +#define NULL ((void*)0) +#endif + +#ifdef maindef +/* + * for MAIN.C + * initialized global definitions + */ + +short kbdm[NKBDM] = {CTLX | ')'}; /* Macro */ +int fillcol = 72; /* Current fill column */ +char pat[NPAT]; /* Search pattern */ +char rpat[NPAT]; /* replacement pattern */ +int revexist = FALSE; +int eolexist = TRUE; /* does clear to EOL exist */ +int sgarbf = TRUE; /* TRUE if screen is garbage */ +int mpresf = FALSE; /* TRUE if message in last line */ + +/* uninitialized global definitions */ + +int currow; /* Cursor row */ +int curcol; /* Cursor column */ +int thisflag; /* Flags, this command */ +int lastflag; /* Flags, last command */ +int curgoal; /* Goal for C-P, C-N */ +WINDOW *curwp; /* Current window */ +BUFFER *curbp; /* Current buffer */ +WINDOW *wheadp; /* Head of list of windows */ +BUFFER *bheadp; /* Head of list of buffers */ +BUFFER *blistp; /* Buffer for C-X C-B */ +short *kbdmip; /* Input pointer for above */ +short *kbdmop; /* Output pointer for above */ + +#else +/* + * for all the other .C files + * initialized global external declarations + */ + +extern int fillcol; /* Fill column */ +extern short kbdm[]; /* Holds kayboard macro data */ +extern char pat[]; /* Search pattern */ +extern char rpat[]; /* Replacement pattern */ +extern int eolexist; /* does clear to EOL exist? */ +extern int revexist; /* does reverse video exist? */ +extern char *modename[]; /* text names of modes */ +extern char modecode[]; /* letters to represent modes */ +extern KEYTAB keytab[]; /* key bind to functions table */ +extern int gmode; /* global editor mode */ +extern int sgarbf; /* State of screen unknown */ +extern int mpresf; /* Stuff in message line */ +extern int clexec; /* command line execution flag */ + +/* initialized global external declarations */ + +extern int currow; /* Cursor row */ +extern int curcol; /* Cursor column */ +extern int thisflag; /* Flags, this command */ +extern int lastflag; /* Flags, last command */ +extern int curgoal; /* Goal for C-P, C-N */ +extern WINDOW *curwp; /* Current window */ +extern BUFFER *curbp; /* Current buffer */ +extern WINDOW *wheadp; /* Head of list of windows */ +extern BUFFER *bheadp; /* Head of list of buffers */ +extern BUFFER *blistp; /* Buffer for C-X C-B */ +extern short *kbdmip; /* Input pointer for above */ +extern short *kbdmop; /* Output pointer for above */ + +#endif + +/* terminal table defined only in TERM.C */ + +#ifndef termdef +extern TERM term; /* Terminal information */ +#endif diff --git a/src/cmd/emg/efunc.h b/src/cmd/emg/efunc.h new file mode 100644 index 0000000..0531b3c --- /dev/null +++ b/src/cmd/emg/efunc.h @@ -0,0 +1,70 @@ +/* This file is in the public domain. */ + +/* EFUNC.H: function declarations and names + * + * This file list all the C code functions used. To add functions, declare it + * here in both the extern function list and the name binding table + */ + +extern int ctrlg(); /* Abort out of things */ +extern int quit(); /* Quit */ +extern int ctlxlp(); /* Begin macro */ +extern int ctlxrp(); /* End macro */ +extern int ctlxe(); /* Execute macro */ +extern int fileread(); /* Get a file, read only */ +extern int filefind(); /* Get a file, read write */ +extern int filewrite(); /* Write a file */ +extern int filesave(); /* Save current file */ +extern int filename(); /* Adjust file name */ +extern int getccol(); /* Get current column */ +extern int gotobol(); /* Move to start of line */ +extern int forwchar(); /* Move forward by characters */ +extern int gotoeol(); /* Move to end of line */ +extern int backchar(); /* Move backward by characters */ +extern int forwline(); /* Move forward by lines */ +extern int backline(); /* Move backward by lines */ +extern int gotobob(); /* Move to start of buffer */ +extern int gotoeob(); /* Move to end of buffer */ +extern int setfillcol(); /* Set fill column */ +extern int setmark(); /* Set mark */ +extern int forwsearch(); /* Search forward */ +extern int backsearch(); /* Search backwards */ +extern int sreplace(); /* search and replace */ +extern int qreplace(); /* search and replace w/query */ +extern int nextwind(); /* Move to the next window */ +extern int prevwind(); /* Move to the previous window */ +extern int onlywind(); /* Make current window only one */ +extern int splitwind(); /* Split current window */ +extern int enlargewind(); /* Enlarge display window */ +extern int shrinkwind(); /* Shrink window */ +extern int listbuffers(); /* Display list of buffers */ +extern int usebuffer(); /* Switch a window to a buffer */ +extern int killbuffer(); /* Make a buffer go away */ +extern int refresh(); /* Refresh the screen */ +extern int twiddle(); /* Twiddle characters */ +extern int tab(); /* Insert tab */ +extern int newline(); /* Insert CR-LF */ +extern int openline(); /* Open up a blank line */ +extern int quote(); /* Insert literal */ +extern int backword(); /* Backup by words */ +extern int forwword(); /* Advance by words */ +extern int forwdel(); /* Forward delete */ +extern int backdel(); /* Backward delete */ +extern int killtext(); /* Kill forward */ +extern int yank(); /* Yank back from killbuffer */ +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 */ +extern int killregion(); /* Kill region */ +extern int copyregion(); /* Copy region to kill buffer */ +extern int quickexit(); /* low keystroke style exit */ +extern int setline(); /* go to a numbered line */ +extern int namebuffer(); /* rename the current buffer */ +extern int deskey(); /* describe a key's binding */ +extern int insfile(); /* insert a file */ +extern int nextbuffer(); /* switch to the next buffer */ +extern int forwhunt(); /* hunt forward for next match */ +extern int backhunt(); /* hunt backwards for next match */ +extern int extendedcmd(); /* parse ANSI/VT100 extended keys */ diff --git a/src/cmd/emg/emg.1 b/src/cmd/emg/emg.1 new file mode 100644 index 0000000..f8cea83 --- /dev/null +++ b/src/cmd/emg/emg.1 @@ -0,0 +1,58 @@ +.\" This file is in the public domain. +.\" +.\" Basic emg man page. +.\" As both Ersatz Emacs and Mg are Public Domain, emg is also Public Domain. +.\" +.Dd March 23, 2014 +.Os +.Dt EMG 1 +.Sh NAME +.Nm emg +.Nd very small Emacs-like text editor +.Sh SYNOPSIS +.Nm emg +.Op Ar +.Sh DESCRIPTION +.Nm , +or Ersatz Mg, is an Emacs-like text editor designed for memory-constrained +environments. +.Nm +was originally created to fit into an operating environment of 96K of RAM, and +one in which Mg did not fit and Ersatz Emacs did not build. +By combining parts of each, a working editor was created. +.Pp +When invoked without file arguments, +.Nm +creates a +.Qq main +buffer. +This buffer must be renamed +.Ic ( C-x n ) +in order to be saved +.Ic ( C-x C-s ) . +.Pp +As both Ersatz Emacs and Mg are Public Domain, +.Nm +is also Public Domain. +.Sh FILES +There is a chart of key bindings in +.Pa /usr/local/share/doc/emg/emg.keys . +Consulting this file is a must, as +.Nm +does not guarantee keybindings to be identical to other Emacs implementations. +.Sh AUTHORS +.Nm +is a combination of Ersatz Emacs and Mg and therefore all authors for both +deserve credit. +Ersatz Emacs and Mg were combined by +.An Brian Callahan Aq Mt bcallah@openbsd.org +to create +.Nm . +.Sh BUGS +None known. +However, patches are appreciated if any are found. +.Sh TODO +It would be nice to have automatic window resizing. +It would also be nice if +.Nm +could clear the screen when quit, like Mg does. diff --git a/src/cmd/emg/emg.keys b/src/cmd/emg/emg.keys new file mode 100644 index 0000000..6677f1b --- /dev/null +++ b/src/cmd/emg/emg.keys @@ -0,0 +1,147 @@ + emg keybindings (March 16, 2014) + Based on Ersatz Emacs (2000/09/14) + +M- means to use the key prior to using another key +^A means to use the control key at the same time as the 'A' key + +------------------------------------------------------------------------------ + MOVING THE CURSOR + +^F Forward character M-F Forward word +^B Backward character M-B Backward word +^N Next line M-P Front of paragraph +^P Previous line M-N End of paragraph +^A Front of line M-< or [HOME] Start of file +^E End of line M-> or [END] End of file +M-G Go to line Arrow keys are active + +------------------------------------------------------------------------------ + DELETING & INSERTING + +<- Delete previous character M-<- Delete previous word +^D Delete next character M-D Delete next word +^K Delete to end of line ^O Insert line + +------------------------------------------------------------------------------ + FORMATTING & TRANSPOSING + +M-U UPPERCASE word M-C Capitalize word +M-L lowercase word ^T Transpose characters +^Q Quote next key, so that control codes may be entered into text. (or ^X Q) +M-Q Format paragraph so that text is left-justified between margins. +^X F Set the right margin for paragraph formatting to the current position of + the cursor. + +------------------------------------------------------------------------------ + SEARCHING + +^S Search forward from cursor position. Type in a string and end it with + ENTER. Either case matches. (or M-S) +^R As above, but reverse search from cursor position. + +------------------------------------------------------------------------------ + REPLACING + +M-R Replace all instances of first typed-in string with second typed-in + string. +M-% Replace with query. Answer with: + Y replace & continue N no replacement & continue + ! replace the rest ? Get a list of options + . exit and return to entry point + ^G,'q' or exit and remain at current location + +------------------------------------------------------------------------------ + COPYING AND MOVING + +^@ or M- Set mark at current position. +^W Delete region. +M-W Copy region to kill buffer. +^Y Yank back kill buffer at cursor. + +A region is defined as the area between this mark and the current cursor +position. The kill buffer is the text which has been most recently deleted or +copied. + +Generally, the procedure for copying or moving text is: +1) Mark out region using M- at the beginning and move the cursor to + the end. +2) Delete it (with ^W) or copy it (with M-W) into the kill buffer. +3) Move the cursor to the desired location and yank it back (with ^Y). + +------------------------------------------------------------------------------ + MULTIPLE BUFFERS + +A buffer contains a COPY of a document being edited, and must be saved for +changes to be kept. Many buffers may be activated at once. + +^X B Switch to another buffer. +^X ^B Show buffer directory in a window (^X 1 to remove). +^X K Delete a non-displayed buffer. +^X X Switch to next buffer in buffer list. +^X N Change the filename associated with the buffer. +M-^N Change the name of the buffer. + +------------------------------------------------------------------------------ + READING FROM DISK + +^X^F Find file; read into a new buffer created from filename. + (This is the usual way to edit a new file.) +^X^R Read file into current buffer, erasing its previous contents. + No new buffer will be created. +^X^I Insert file into current buffer at cursor's location. + +------------------------------------------------------------------------------ + SAVING TO DISK + +^X^S Save current buffer to disk, using the buffer's filename as the name of + the disk file. Any disk file of that name will be overwritten. (or ^X S) +^X^W Write current buffer to disk. Type in a new filename at the prompt to + write to; it will also become the current buffer's filename. + +------------------------------------------------------------------------------ + MULTIPLE WINDOWS + +Many windows may be visible at once on the screen. Windows may show different +parts of the same buffer, or each may display a different one. + +^X 2 Split the current window in two ^X 1 Show only current window +^X O Move cursor to next window ^X ^ Enlarge current window +M-^V Scroll other window down M-^Z Scroll other window up + +------------------------------------------------------------------------------ + EXITING + +^X^C Exit. Any unsaved files will require confirmation. +M-Z Write out all changed buffers automatically and exit. + +------------------------------------------------------------------------------ + MACROS + +^X ( Start recording a keyboard macro. Typing ^G or an error aborts. +^X ) Stop recording macro. +^X E Execute macro. + +------------------------------------------------------------------------------ + REPEAT & NUMBER PREFIX + +^U or M- + Number prefix and universal repeat. May be followed by an integer + (default = 4) and repeats the next command that many times. + Exceptions follow. +^U^L + Reposition the cursor to a particular screen row; i.e., ^U0^L moves the + cursor and the line it is on to the top of the screen. Negative numbers + are from the bottom of the screen. +^U^X F + Set the right margin to column for paragraph formatting. +^U^X^ + Enlarge a split window by rows. A negative number shrinks the + window. + +------------------------------------------------------------------------------ + SPECIAL KEYS + +^G Cancel current command. +^L Redraws the screen completely. + +------------------------------------------------------------------------------ diff --git a/src/cmd/emg/estruct.h b/src/cmd/emg/estruct.h new file mode 100644 index 0000000..a5a84ef --- /dev/null +++ b/src/cmd/emg/estruct.h @@ -0,0 +1,162 @@ +/* This file is in the public domain. */ + +/* ESTRUCT: Structure and preprocessor */ + +/* internal constants */ +#define NFILEN 80 /* maximum # of bytes, file name */ +#define NBUFN 16 /* maximum # of bytes, buffer name */ +#define NLINE 512 /* maximum # of bytes, line */ +#define NKBDM 256 /* maximum # of strokes, keyboard macro */ +#define NPAT 80 /* maximum # of bytes, pattern */ +#define HUGE 32700 /* Huge number for "impossible" row&col */ + +#define METACH 0x1B /* M- prefix, Control-[, ESC */ +#define BELL 0x07 /* a bell character */ +#define TAB 0x09 /* a tab character */ + +#define CTRL 0x0100 /* Control flag, or'ed in */ +#define META 0x0200 /* Meta flag, or'ed in */ +#define CTLX 0x0400 /* ^X flag, or'ed in */ + +#define FALSE 0 /* False, no, bad, etc */ +#define TRUE 1 /* True, yes, good, etc */ +#define ABORT 2 /* Death, ^G, abort, etc */ + +#define FIOSUC 0 /* File I/O, success */ +#define FIOFNF 1 /* File I/O, file not found */ +#define FIOEOF 2 /* File I/O, end of file */ +#define FIOERR 3 /* File I/O, error */ +#define FIOLNG 4 /* line longer than allowed len */ + +#define CFCPCN 0x0001 /* Last command was C-P, C-N */ +#define CFKILL 0x0002 /* Last command was a kill */ + +/* + * 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 +{ + struct WINDOW *w_wndp; /* Next window */ + struct BUFFER *w_bufp; /* Buffer displayed in window */ + struct LINE *w_linep; /* Top line in the window */ + struct LINE *w_dotp; /* Line containing "." */ + long w_doto; /* Byte offset for "." */ + struct LINE *w_markp; /* Line containing "mark" */ + long 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 */ + int w_dotline; /* current line number of dot */ +} WINDOW; + +#define WFFORCE 0x01 /* Window needs forced 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". Buffers may be "Inactive" which means the + * files accosiated with them have not been read in yet. These get read in at + * "use buffer" time + */ +typedef struct BUFFER +{ + struct BUFFER *b_bufp; /* Link to next BUFFER */ + struct LINE *b_dotp; /* Link to "." LINE structure */ + long b_doto; /* Offset of "." in above LINE */ + struct LINE *b_markp; /* The same as the above two, */ + long b_marko; /* but for the "mark" */ + struct LINE *b_linep; /* Link to the header LINE */ + char b_active; /* window activated flag */ + char b_nwnd; /* Count of windows on buffer */ + char b_flag; /* Flags */ + char b_fname[NFILEN]; /* File name */ + char b_bname[NBUFN]; /* Buffer name */ + int b_lines; /* Number of lines in file */ +} BUFFER; + +#define BFTEMP 0x01 /* Internal temporary buffer */ +#define BFCHG 0x02 /* Changed since last write */ + +/* + * The starting position of a region, and the size of the region in + * characters, is kept in a region structure. Used by the region commands + */ +typedef struct +{ + struct LINE *r_linep; /* Origin LINE address */ + long r_offset; /* Origin LINE offset */ + long r_size; /* Length in characters */ +} REGION; + +/* + * 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 */ + char l_text[1]; /* A bunch of characters */ +} LINE; + +#define lforw(lp) ((lp)->l_fp) +#define lback(lp) ((lp)->l_bp) +#define lgetc(lp, n) ((lp)->l_text[(n)]&0xFF) +#define lputc(lp, n, c) ((lp)->l_text[(n)]=(c)) +#define llength(lp) ((lp)->l_used) + +/* + * The editor communicates with the display using a high level interface. A + * "TERM" structure holds useful variables, and indirect pointers to routines + * that do useful operations. The low level get and put routines are here too. + * This lets a terminal, in addition to having non standard commands, have + * funny get and put character code too. The calls might get changed to + * "termp->t_field" style in the future, to make it possible to run more than + * one terminal type + */ +typedef struct +{ + int t_nrow; /* Number of rows */ + int t_ncol; /* Number of columns */ + int t_margin; /* min margin for extended lines */ + int t_scrsiz; /* size of scroll region " */ + void (*t_open) (); /* Open terminal at the start */ + void (*t_close) (); /* Close terminal at end */ + int (*t_getchar) (); /* Get character from keyboard */ + void (*t_putchar) (); /* Put character to display */ + void (*t_flush) (); /* Flush output buffers */ + void (*t_move) (); /* Move the cursor, origin 0 */ + void (*t_eeol) (); /* Erase to end of line */ + void (*t_eeop) (); /* Erase to end of page */ + void (*t_beep) (); /* Beep */ + void (*t_rev) (); /* set reverse video state */ +} TERM; + +/* structure for the table of initial key bindings */ + +typedef struct +{ + short k_code; /* Key code */ + int (*k_fp) (); /* Routine to handle it */ +} KEYTAB; diff --git a/src/cmd/emg/file.c b/src/cmd/emg/file.c new file mode 100644 index 0000000..326b823 --- /dev/null +++ b/src/cmd/emg/file.c @@ -0,0 +1,465 @@ +/* This file is in the public domain. */ + +/* + * The routines in this file handle the reading and writing of disk files. + * All details about the reading and writing of the disk are in "fileio.c" + */ + +#include /* strncpy(3) */ +#include "estruct.h" +#include "edef.h" + +extern int mlreply(char *prompt, char *buf, int nbuf); +extern int swbuffer(BUFFER *bp); +extern void mlwrite(); +extern int bclear(BUFFER *bp); +extern int ffropen(char *fn); +extern int ffgetline(char buf[], int nbuf); +extern int ffwopen(char *fn); +extern int ffclose(); +extern int ffputline(char buf[], int nbuf); +extern BUFFER *bfind(); +extern LINE *lalloc(); + +int fileread(int f, int n); +int insfile(int f, int n); +int filefind(int f, int n); +int getfile(char fname[]); +int readin(char fname[]); +void makename(char bname[], char fname[]); +int filewrite(int f, int n); +int filesave(int f, int n); +int writeout(char *fn); +int filename(int f, int n); +int ifile(char fname[]); + +/* + * Read a file into the current buffer. This is really easy; all you do it + * find the name of the file, and call the standard "read a file into the + * current buffer" code. Bound to "C-X C-R" + */ +int fileread(int f, int n) +{ + int s; + char fname[NFILEN]; + + if ((s = mlreply("Read file: ", fname, NFILEN)) != TRUE) + return (s); + return (readin(fname)); +} + +/* + * Insert a file into the current buffer. This is really easy; all you do it + * find the name of the file, and call the standard "insert a file into the + * current buffer" code. Bound to "C-X C-I". + */ +int insfile(int f, int n) +{ + int s; + char fname[NFILEN]; + + if ((s = mlreply("Insert file: ", fname, NFILEN)) != TRUE) + return (s); + return (ifile(fname)); +} + +/* + * 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. Bound to C-X C-F. + */ +int filefind(int f, int n) +{ + char fname[NFILEN]; /* file user wishes to find */ + int s; /* status return */ + + if ((s = mlreply("Find file: ", fname, NFILEN)) != TRUE) + return (s); + return (getfile(fname)); +} + +int getfile(char fname[]) +{ + BUFFER *bp; + LINE *lp; + char bname[NBUFN]; /* buffer name to put file */ + int i, s; + + for (bp = bheadp; bp != (BUFFER*)0; bp = bp->b_bufp) + { + if ((bp->b_flag & BFTEMP) == 0 && strcmp(bp->b_fname, fname) == 0) + { + if (--curbp->b_nwnd == 0) + { + curbp->b_dotp = curwp->w_dotp; + curbp->b_doto = curwp->w_doto; + curbp->b_markp = curwp->w_markp; + curbp->b_marko = curwp->w_marko; + } + swbuffer(bp); + lp = curwp->w_dotp; + i = curwp->w_ntrows / 2; + while (i-- && lback(lp) != curbp->b_linep) + lp = lback(lp); + curwp->w_linep = lp; + curwp->w_flag |= WFMODE | WFHARD; + mlwrite("[Old buffer]"); + return (TRUE); + } + } + makename(bname, fname); /* New buffer name */ + while ((bp = bfind(bname, FALSE, 0)) != (BUFFER*)0) + { + s = mlreply("Buffer name: ", bname, NBUFN); + if (s == ABORT) /* ^G to just quit */ + return (s); + if (s == FALSE) + { /* CR to clobber it */ + makename(bname, fname); + break; + } + } + if (bp == (BUFFER*)0 && (bp = bfind(bname, TRUE, 0)) == (BUFFER*)0) + { + mlwrite("Cannot create buffer"); + return (FALSE); + } + if (--curbp->b_nwnd == 0) + { /* Undisplay */ + curbp->b_dotp = curwp->w_dotp; + curbp->b_doto = curwp->w_doto; + curbp->b_markp = curwp->w_markp; + curbp->b_marko = curwp->w_marko; + } + curbp = bp; /* Switch to it */ + curwp->w_bufp = bp; + curbp->b_nwnd++; + return (readin(fname)); /* Read it in */ +} + +/* + * Read file "fname" into the current buffer, blowing away any text found + * there. Called by both the read and find commands. Return the final status + * of the read. Also called by the mainline, to read in a file specified on + * the command line as an argument. + */ +int readin(char fname[]) +{ + LINE *lp1, *lp2; + WINDOW *wp; + BUFFER *bp; + char line[NLINE]; + int nbytes, s, i; + int nline = 0; /* initialize here to silence a gcc warning */ + int lflag; /* any lines longer than allowed? */ + + bp = curbp; /* Cheap */ + if ((s = bclear(bp)) != TRUE) /* Might be old */ + return (s); + bp->b_flag &= ~(BFTEMP | BFCHG); + strncpy(bp->b_fname, fname, NFILEN); + if ((s = ffropen(fname)) == FIOERR) /* Hard file open */ + goto out; + if (s == FIOFNF) + { /* File not found */ + mlwrite("[New file]"); + goto out; + } + mlwrite("[Reading file]"); + lflag = FALSE; + while ((s = ffgetline(line, NLINE)) == FIOSUC || s == FIOLNG) + { + if (s == FIOLNG) + lflag = TRUE; + nbytes = strlen(line); + if ((lp1 = lalloc(nbytes)) == NULL) + { + s = FIOERR; /* Keep message on the display */ + break; + } + lp2 = lback(curbp->b_linep); + lp2->l_fp = lp1; + lp1->l_fp = curbp->b_linep; + lp1->l_bp = lp2; + curbp->b_linep->l_bp = lp1; + for (i = 0; i < nbytes; ++i) + lputc(lp1, i, line[i]); + ++nline; + } + ffclose(); /* Ignore errors */ + if (s == FIOEOF) + { /* Don't zap message! */ + if (nline != 1) + mlwrite("[Read %d lines]", nline); + else + mlwrite("[Read 1 line]"); + } + if (lflag) + { + if (nline != 1) + mlwrite("[Read %d lines: Long lines wrapped]", nline); + else + mlwrite("[Read 1 line: Long lines wrapped]"); + } + curwp->w_bufp->b_lines = nline; + out: + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) + { + if (wp->w_bufp == curbp) + { + wp->w_linep = lforw(curbp->b_linep); + wp->w_dotp = lforw(curbp->b_linep); + wp->w_doto = 0; + wp->w_markp = NULL; + wp->w_marko = 0; + wp->w_flag |= WFMODE | WFHARD; + } + } + if (s == FIOERR || s == FIOFNF) /* False if error */ + return (FALSE); + return (TRUE); +} + +/* + * Take a file name, and from it fabricate a buffer name. This routine knows + * about the syntax of file names on the target system. I suppose that this + * information could be put in a better place than a line of code. + */ +void makename(char bname[], char fname[]) +{ + char *cp1, *cp2; + + cp1 = &fname[0]; + while (*cp1 != 0) + ++cp1; + + while (cp1 != &fname[0] && cp1[-1] != '/') + --cp1; + cp2 = &bname[0]; + while (cp2 != &bname[NBUFN - 1] && *cp1 != 0 && *cp1 != ';') + *cp2++ = *cp1++; + *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. Bound to "C-X C-W". + */ +int filewrite(int f, int n) +{ + WINDOW *wp; + char fname[NFILEN]; + int s; + + if ((s = mlreply("Write file: ", fname, NFILEN)) != TRUE) + return (s); + if ((s = writeout(fname)) == TRUE) + { + strncpy(curbp->b_fname, fname, NFILEN); + 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; + } + } + return (s); +} + +/* + * Save the contents of the current buffer in its associatd file. No nothing + * if nothing has changed (this may be a bug, not a feature). Error if there + * is no remembered file name for the buffer. Bound to "C-X C-S". May get + * called by "C-Z" + */ +int filesave(int f, int n) +{ + WINDOW *wp; + int s; + + if ((curbp->b_flag & BFCHG) == 0) /* Return, no changes */ + return (TRUE); + if (curbp->b_fname[0] == 0) + { /* Must have a name */ + mlwrite("No file name"); + return (FALSE); + } + if ((s = writeout(curbp->b_fname)) == TRUE) + { + 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; + } + } + return (s); +} + +/* + * This function performs the details of file writing. Uses the file + * management routines in the "fileio.c" package. The number of lines written + * is displayed. Sadly, it looks inside a LINE; provide a macro for this. Most + * of the grief is error checking of some sort. + */ +int writeout(char *fn) +{ + LINE *lp; + int nline, s; + + if ((s = ffwopen(fn)) != FIOSUC) /* Open writes message */ + return (FALSE); + mlwrite("[Writing]"); /* tell us were writing */ + lp = lforw(curbp->b_linep); /* First line */ + nline = 0; /* Number of lines */ + while (lp != curbp->b_linep) + { + if ((s = ffputline(&lp->l_text[0], llength(lp))) != FIOSUC) + break; + ++nline; + lp = lforw(lp); + } + if (s == FIOSUC) + { /* No write error */ + s = ffclose(); + if (s == FIOSUC) + { /* No close error */ + if (nline != 1) + mlwrite("[Wrote %d lines]", nline); + else + mlwrite("[Wrote 1 line]"); + } + } + else /* ignore close error */ + ffclose(); /* if a write error */ + if (s != FIOSUC) /* some sort of error */ + return (FALSE); + return (TRUE); +} + +/* + * The command allows the user to modify the file name associated with the + * current buffer. It is like the "f" command in UNIX "ed". The operation is + * simple; just zap the name in the BUFFER structure, and mark the windows as + * needing an update. You can type a blank line at the prompt if you wish. + */ +int filename(int f, int n) +{ + WINDOW *wp; + char fname[NFILEN]; + int s; + + if ((s = mlreply("Name: ", fname, NFILEN)) == ABORT) + return (s); + if (s == FALSE) + strncpy(curbp->b_fname, "", 1); + else + strncpy(curbp->b_fname, fname, NFILEN); + wp = wheadp; /* update mode lines */ + while (wp != NULL) + { + if (wp->w_bufp == curbp) + wp->w_flag |= WFMODE; + wp = wp->w_wndp; + } + return (TRUE); +} + +/* + * Insert file "fname" into the current buffer, Called by insert file command. + * Return the final status of the read. + */ +int ifile(char fname[]) +{ + LINE *lp0, *lp1, *lp2; + BUFFER *bp; + char line[NLINE]; + int i, s, nbytes; + int nline = 0; + int lflag; /* any lines longer than allowed? */ + + bp = curbp; /* Cheap */ + bp->b_flag |= BFCHG; /* we have changed */ + bp->b_flag &= ~BFTEMP; /* and are not temporary */ + if ((s = ffropen(fname)) == FIOERR) /* Hard file open */ + goto out; + if (s == FIOFNF) + { /* File not found */ + mlwrite("[No such file]"); + return (FALSE); + } + mlwrite("[Inserting file]"); + + /* back up a line and save the mark here */ + curwp->w_dotp = lback(curwp->w_dotp); + curwp->w_doto = 0; + curwp->w_markp = curwp->w_dotp; + curwp->w_marko = 0; + + lflag = FALSE; + while ((s = ffgetline(line, NLINE)) == FIOSUC || s == FIOLNG) + { + if (s == FIOLNG) + lflag = TRUE; + nbytes = strlen(line); + if ((lp1 = lalloc(nbytes)) == NULL) + { + s = FIOERR; /* keep message on the */ + break; /* display */ + } + lp0 = curwp->w_dotp; /* line previous to insert */ + lp2 = lp0->l_fp; /* line after insert */ + + /* re-link new line between lp0 and lp2 */ + lp2->l_bp = lp1; + lp0->l_fp = lp1; + lp1->l_bp = lp0; + lp1->l_fp = lp2; + + /* and advance and write out the current line */ + curwp->w_dotp = lp1; + for (i = 0; i < nbytes; ++i) + lputc(lp1, i, line[i]); + ++nline; + } + ffclose(); /* Ignore errors */ + curwp->w_markp = lforw(curwp->w_markp); + if (s == FIOEOF) + { /* Don't zap message! */ + if (nline != 1) + mlwrite("[Inserted %d lines]", nline); + else + mlwrite("[Inserted 1 line]"); + } + if (lflag) + { + if (nline != 1) + mlwrite("[Inserted %d lines: Long lines wrapped]", nline); + else + mlwrite("[Inserted 1 line: Long lines wrapped]"); + } + out: + /* advance to the next line and mark the window for changes */ + curwp->w_dotp = lforw(curwp->w_dotp); + curwp->w_flag |= WFHARD; + + /* copy window parameters back to the buffer structure */ + curbp->b_dotp = curwp->w_dotp; + curbp->b_doto = curwp->w_doto; + curbp->b_markp = curwp->w_markp; + curbp->b_marko = curwp->w_marko; + + /* we need to update number of lines in the buffer */ + curwp->w_bufp->b_lines += nline; + + if (s == FIOERR) /* False if error */ + return (FALSE); + return (TRUE); +} diff --git a/src/cmd/emg/fileio.c b/src/cmd/emg/fileio.c new file mode 100644 index 0000000..d61b005 --- /dev/null +++ b/src/cmd/emg/fileio.c @@ -0,0 +1,121 @@ +/* This file is in the public domain. */ + +/* + * The routines in this file read and write ASCII files from the disk. All of + * the knowledge about files is here. A better message writing scheme should + * be used + */ + +#include /* fopen(3), et.al. */ +#include "estruct.h" + +extern void mlwrite(); + +int ffropen(char *fn); +int ffwopen(char *fn); +int ffclose(); +int ffputline(char buf[], int nbuf); +int ffgetline(char buf[], int nbuf); + +FILE *ffp; /* File pointer, all functions */ + +/* + * Open a file for reading. + */ +int ffropen(char *fn) +{ + if ((ffp = fopen(fn, "r")) == NULL) + return (FIOFNF); + return (FIOSUC); +} + +/* + * Open a file for writing. Return TRUE if all is well, and FALSE on error + * (cannot create). + */ +int ffwopen(char *fn) +{ + if ((ffp = fopen(fn, "w")) == NULL) + { + mlwrite("Cannot open file for writing"); + return (FIOERR); + } + return (FIOSUC); +} + +/* + * Close a file. Should look at the status in all systems. + */ +int ffclose() +{ + if (fclose(ffp) != FALSE) + { + mlwrite("Error closing file"); + return (FIOERR); + } + return (FIOSUC); +} + +/* + * Write a line to the already opened file. The "buf" points to the buffer, + * and the "nbuf" is its length, less the free newline. Return the status. + * Check only at the newline. + */ +int ffputline(char buf[], int nbuf) +{ + int i; + + for (i = 0; i < nbuf; ++i) + fputc(buf[i] & 0xFF, ffp); + + fputc('\n', ffp); + + if (ferror(ffp)) + { + mlwrite("Write I/O error"); + return (FIOERR); + } + return (FIOSUC); +} + +/* + * Read a line from a file, and store the bytes in the supplied buffer. The + * "nbuf" is the length of the buffer. Complain about long lines and lines at + * the end of the file that don't have a newline present. Check for I/O errors + * too. Return status. + */ +int ffgetline(char buf[], int nbuf) +{ + int c, i; + + i = 0; + + while ((c = fgetc(ffp)) != EOF && c != '\n') + { + if (i >= nbuf - 2) + { + buf[nbuf - 2] = c; /* store last char read */ + buf[nbuf - 1] = 0; /* and terminate it */ + mlwrite("File has long lines"); + return (FIOLNG); + } + buf[i++] = c; + } + + if (c == EOF) + { + if (ferror(ffp)) + { + mlwrite("File read error"); + return (FIOERR); + } + if (i != 0) + { + mlwrite("No newline at EOF"); + return (FIOERR); + } + return (FIOEOF); + } + buf[i] = 0; + return (FIOSUC); +} diff --git a/src/cmd/emg/line.c b/src/cmd/emg/line.c new file mode 100644 index 0000000..df05774 --- /dev/null +++ b/src/cmd/emg/line.c @@ -0,0 +1,507 @@ +/* This file is in the public domain. */ + +/* + * 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 /* malloc(3) */ +#include "estruct.h" +#include "edef.h" + +extern void mlwrite(); +extern int backchar(int f, int n); + +LINE* lalloc(int used); +void lfree(LINE *lp); +void lchange(int flag); +int linsert(int n, int c); +int lnewline(); +int ldelete(int n, int kflag); +int ldelnewline(); +void kdelete(); +int kinsert(int c); +int kremove(int n); + +#define NBLOCK 16 /* Line block chunk size */ +#define KBLOCK 1024 /* Kill buffer block size */ + +char *kbufp = NULL; /* Kill buffer data */ +unsigned long kused = 0; /* # of bytes used in KB */ +unsigned long ksize = 0; /* # of bytes allocated in KB */ + +/* + * This routine allocates a block of memory large enough to hold a LINE + * containing "used" characters. The block is always rounded up a bit. 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(int used) +{ + LINE *lp; + int size; + + size = (used + NBLOCK - 1) & ~(NBLOCK - 1); + if (size == 0) /* Assume that an empty */ + size = NBLOCK; /* line is for type-in */ + if ((lp = (LINE *) malloc(sizeof(LINE) + size)) == NULL) + { + mlwrite("Cannot allocate %d bytes", size); + return (NULL); + } + lp->l_size = size; + 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(LINE *lp) +{ + BUFFER *bp; + WINDOW *wp; + + wp = wheadp; + while (wp != NULL) + { + 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; + } + wp = wp->w_wndp; + } + bp = bheadp; + while (bp != NULL) + { + 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; + } + } + bp = bp->b_bufp; + } + 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 t HARD. Set MODE if + * the mode line needs to be updated (the "*" has to be set). + */ +void lchange(int flag) +{ + WINDOW *wp; + + if (curbp->b_nwnd != 1) /* Ensure hard */ + flag = WFHARD; + if ((curbp->b_flag & BFCHG) == 0) + { /* First change, so */ + flag |= WFMODE; /* update mode lines */ + curbp->b_flag |= BFCHG; + } + wp = wheadp; + while (wp != NULL) + { + if (wp->w_bufp == curbp) + wp->w_flag |= flag; + wp = wp->w_wndp; + } +} + +/* + * Insert "n" copies of the character "c" at the current location of dot. In + * the easy case all that happens is the text is stored in the line. In the + * hard case, the line has to be reallocated. When the window list is updated, + * take special care; I screwed it up once. You always update dot in the + * current window. You update mark, and a dot in another window, if it is + * greater than the place where you did the insert. Return TRUE if all is + * well, and FALSE on errors + */ +int linsert(int n, int c) +{ + WINDOW *wp; + LINE *lp1, *lp2, *lp3; + char *cp1, *cp2; + int i, doto; + + lchange(WFEDIT); + lp1 = curwp->w_dotp; /* Current line */ + if (lp1 == curbp->b_linep) + { /* At the end: special */ + if (curwp->w_doto != 0) + { + mlwrite("Bug: linsert"); + return (FALSE); + } + if ((lp2 = lalloc(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; + curwp->w_dotp = lp2; + curwp->w_doto = n; + return (TRUE); + } + doto = curwp->w_doto; /* Save for later */ + if (lp1->l_used + n > lp1->l_size) + { /* Hard: reallocate */ + if ((lp2 = lalloc(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++; + 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 */ + 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; + wp = wheadp; /* Update windows */ + while (wp != NULL) + { + 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) + wp->w_doto += n; + } + if (wp->w_markp == lp1) + { + wp->w_markp = lp2; + if (wp->w_marko > doto) + wp->w_marko += n; + } + wp = wp->w_wndp; + } + return (TRUE); +} + +/* + * Insert a newline into the buffer at the current location of dot in the + * current window. The funny ass-backwards way it does things is not a botch; + * it just makes the last line in the file not a special case. Return TRUE if + * everything works out and FALSE on error (memory allocation failure). The + * update of dot and mark is a bit easier then in the above case, because the + * split forces more updating. + */ +int lnewline() +{ + WINDOW *wp; + char *cp1, *cp2; + LINE *lp1, *lp2; + int doto; + + lchange(WFHARD); + + curwp->w_bufp->b_lines++; + + lp1 = curwp->w_dotp; /* Get the address and */ + doto = curwp->w_doto; /* offset of "." */ + if ((lp2 = lalloc(doto)) == NULL) /* New first half line */ + return (FALSE); + cp1 = &lp1->l_text[0]; /* Shuffle text around */ + cp2 = &lp2->l_text[0]; + while (cp1 != &lp1->l_text[doto]) + *cp2++ = *cp1++; + cp2 = &lp1->l_text[0]; + while (cp1 != &lp1->l_text[lp1->l_used]) + *cp2++ = *cp1++; + lp1->l_used -= doto; + lp2->l_bp = lp1->l_bp; + lp1->l_bp = lp2; + lp2->l_bp->l_fp = lp2; + lp2->l_fp = lp1; + wp = wheadp; /* Windows */ + while (wp != NULL) + { + if (wp->w_linep == lp1) + wp->w_linep = lp2; + if (wp->w_dotp == lp1) + { + if (wp->w_doto < doto) + wp->w_dotp = lp2; + else + wp->w_doto -= doto; + } + if (wp->w_markp == lp1) + { + if (wp->w_marko < doto) + wp->w_markp = lp2; + else + wp->w_marko -= doto; + } + wp = wp->w_wndp; + } + curwp->w_dotline++; + 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" is TRUE if the text should be put in the kill buffer. + */ +int ldelete(int n, int kflag) +{ + LINE *dotp; + WINDOW *wp; + char *cp1, *cp2; + int doto, chunk; + + 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 */ + lchange(WFHARD); + if (ldelnewline() == FALSE + || (kflag != FALSE && kinsert('\n') == FALSE)) + return (FALSE); + --n; + continue; + } + lchange(WFEDIT); + cp1 = &dotp->l_text[doto]; /* Scrunch text */ + cp2 = cp1 + chunk; + if (kflag != FALSE) + { /* Kill? */ + while (cp1 != cp2) + { + if (kinsert (*cp1) == FALSE) + return (FALSE); + ++cp1; + } + cp1 = &dotp->l_text[doto]; + } + while (cp2 != &dotp->l_text[dotp->l_used]) + *cp1++ = *cp2++; + dotp->l_used -= chunk; + wp = wheadp; /* Fix windows */ + while (wp != NULL) + { + if (wp->w_dotp == dotp && wp->w_doto >= doto) + { + wp->w_doto -= chunk; + if (wp->w_doto < doto) + wp->w_doto = doto; + } + if (wp->w_markp == dotp && wp->w_marko >= doto) + { + wp->w_marko -= chunk; + if (wp->w_marko < doto) + wp->w_marko = doto; + } + wp = wp->w_wndp; + } + 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. Called by "ldelete" only. + */ +int ldelnewline() +{ + LINE *lp1, *lp2, *lp3; + WINDOW *wp; + char *cp1, *cp2; + + lp1 = curwp->w_dotp; + lp2 = lp1->l_fp; + if (lp2 == curbp->b_linep) + { /* At the buffer end */ + if (lp1->l_used == 0) /* Blank line */ + lfree(lp1); + return (TRUE); + } + /* Keep line counts in sync */ + curwp->w_bufp->b_lines--; + if (lp2->l_used <= lp1->l_size - lp1->l_used) + { + cp1 = &lp1->l_text[lp1->l_used]; + cp2 = &lp2->l_text[0]; + while (cp2 != &lp2->l_text[lp2->l_used]) + *cp1++ = *cp2++; + wp = wheadp; + while (wp != NULL) + { + 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; + } + wp = wp->w_wndp; + } + 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); + cp1 = &lp1->l_text[0]; + cp2 = &lp3->l_text[0]; + while (cp1 != &lp1->l_text[lp1->l_used]) + *cp2++ = *cp1++; + cp1 = &lp2->l_text[0]; + while (cp1 != &lp2->l_text[lp2->l_used]) + *cp2++ = *cp1++; + lp1->l_bp->l_fp = lp3; + lp3->l_fp = lp2->l_fp; + lp2->l_fp->l_bp = lp3; + lp3->l_bp = lp1->l_bp; + wp = wheadp; + while (wp != NULL) + { + 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; + } + wp = wp->w_wndp; + } + free((char *) lp1); + free((char *) lp2); + 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; + kused = 0; + 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. + */ +int kinsert(int c) +{ + char *nbufp; + + if (kused == ksize) + { + if (ksize == 0) /* first time through? */ + nbufp = malloc(KBLOCK); /* alloc the first block */ + else /* or re allocate a bigger block */ + nbufp = realloc(kbufp, ksize + KBLOCK); + if (nbufp == NULL) /* abort if it fails */ + return (FALSE); + kbufp = nbufp; /* point our global at it */ + ksize += KBLOCK; /* and adjust the size */ + } + kbufp[kused++] = c; + return (TRUE); +} + +/* + * This function gets characters from the kill buffer. If the character index + * "n" is off the end, it returns "-1". This lets the caller just scan along + * until it gets a "-1" back. + */ +int kremove(int n) +{ + if (n >= kused) + return (-1); + else + return (kbufp[n] & 0xFF); +} diff --git a/src/cmd/emg/main.c b/src/cmd/emg/main.c new file mode 100644 index 0000000..880c410 --- /dev/null +++ b/src/cmd/emg/main.c @@ -0,0 +1,465 @@ +/* This file is in the public domain. */ + +/* + * This program is in public domain; originally written by Dave G. Conroy. + * This file contains the main driving routine, and some keyboard processing + * code + */ + +#define maindef /* make global definitions not external */ + +#include /* strncpy(3) */ +#include /* malloc(3) */ +#include "estruct.h" /* global structures and defines */ +#include "efunc.h" /* function declarations and name table */ +#include "edef.h" /* global definitions */ +#include "ebind.h" + +extern void getwinsize(); +extern void vtinit(); +extern void vttidy(); +extern void update(); +extern void mlerase(); +extern void mlwrite(); +extern int mlyesno(char *prompt); +extern void makename(char bname[], char fname[]); +extern int readin(char fname[]); +extern int linsert(int f, int n); +extern int anycb(); +extern BUFFER *bfind(); + +void edinit(char bname[]); +int execute(int c, int f, int n); +int getkey(); +int getctl(); +int quickexit(int f, int n); +int quit(int f, int n); +int ctlxlp(int f, int n); +int ctlxrp(int f, int n); +int ctlxe(int f, int n); +int ctrlg(int f, int n); +int extendedcmd(int f, int n); + +int main(int argc, char *argv[]) +{ + BUFFER *bp; + char bname[NBUFN]; /* buffer name of file to read */ + int c, f, n, mflag; + int ffile; /* first file flag */ + int carg; /* current arg to scan */ + int basec; /* c stripped of meta character */ + + /* initialize the editor and process the startup file */ + getwinsize(); /* find out the "real" screen size */ + strncpy(bname, "main", 5); /* default buffer name */ + edinit(bname); /* Buffers, windows */ + vtinit(); /* Displays */ + ffile = TRUE; /* no file to edit yet */ + update(); /* let the user know we are here */ + + /* scan through the command line and get the files to edit */ + for (carg = 1; carg < argc; ++carg) + { + /* set up a buffer for this file */ + makename(bname, argv[carg]); + + /* if this is the first file, read it in */ + if (ffile) + { + bp = curbp; + makename(bname, argv[carg]); + strncpy(bp->b_bname, bname, NBUFN); + strncpy(bp->b_fname, argv[carg], NFILEN); + if (readin(argv[carg]) == ABORT) + { + strncpy(bp->b_bname, "main", 5); + strncpy(bp->b_fname, "", 1); + } + bp->b_dotp = bp->b_linep; + bp->b_doto = 0; + ffile = FALSE; + } + else + { + /* set this to inactive */ + bp = bfind(bname, TRUE, 0); + strncpy(bp->b_fname, argv[carg], NFILEN); + bp->b_active = FALSE; + } + } + + /* setup to process commands */ + lastflag = 0; /* Fake last flags */ + curwp->w_flag |= WFMODE; /* and force an update */ + + loop: + update(); /* Fix up the screen */ + c = getkey(); + if (mpresf != FALSE) + { + mlerase(); + update(); + } + f = FALSE; + n = 1; + + /* do META-# processing if needed */ + + basec = c & ~META; /* strip meta char off if there */ + if ((c & META) && ((basec >= '0' && basec <= '9') || basec == '-')) + { + f = TRUE; /* there is a # arg */ + n = 0; /* start with a zero default */ + mflag = 1; /* current minus flag */ + c = basec; /* strip the META */ + while ((c >= '0' && c <= '9') || (c == '-')) + { + if (c == '-') + { + /* already hit a minus or digit? */ + if ((mflag == -1) || (n != 0)) + break; + mflag = -1; + } + else + n = n * 10 + (c - '0'); + if ((n == 0) && (mflag == -1)) /* lonely - */ + mlwrite("Arg:"); + else + mlwrite("Arg: %d", n * mflag); + + c = getkey(); /* get the next key */ + } + n = n * mflag; /* figure in the sign */ + } + /* do ^U repeat argument processing */ + + if (c == (CTRL | 'U')) + { /* ^U, start argument */ + f = TRUE; + n = 4; /* with argument of 4 */ + mflag = 0; /* that can be discarded */ + mlwrite("Arg: 4"); + while (((c = getkey ()) >= '0') + && ((c <= '9') || (c == (CTRL | 'U')) || (c == '-'))) + { + if (c == (CTRL | 'U')) + n = n * 4; + /* + * If dash, and start of argument string, set arg. + * to -1. Otherwise, insert it. + */ + else if (c == '-') + { + if (mflag) + break; + n = 0; + mflag = -1; + } + /* + * If first digit entered, replace previous argument + * with digit and set sign. Otherwise, append to arg. + */ + else + { + if (!mflag) + { + n = 0; + mflag = 1; + } + n = 10 * n + c - '0'; + } + mlwrite("Arg: %d", (mflag >= 0) ? n : (n ? -n : -1)); + } + /* + * Make arguments preceded by a minus sign negative and change + * the special argument "^U -" to an effective "^U -1". + */ + if (mflag == -1) + { + if (n == 0) + n++; + n = -n; + } + } + + if (c == (CTRL | 'X')) /* ^X is a prefix */ + c = CTLX | getctl (); + if (kbdmip != NULL) + { /* Save macro strokes */ + if (c != (CTLX | ')') && kbdmip > &kbdm[NKBDM - 6]) + { + ctrlg(FALSE, 0); + goto loop; + } + if (f != FALSE) + { + *kbdmip++ = (CTRL | 'U'); + *kbdmip++ = n; + } + *kbdmip++ = c; + } + execute(c, f, n); /* Do it */ + goto loop; +} + +/* + * Initialize all of the buffers and windows. The buffer name is passed down + * as an argument, because the main routine may have been told to read in a + * file by default, and we want the buffer name to be right. + */ +void edinit(char bname[]) +{ + BUFFER *bp; + WINDOW *wp; + + bp = bfind(bname, TRUE, 0); /* First buffer */ + blistp = bfind("[List]", TRUE, BFTEMP); /* Buffer list buffer */ + wp = (WINDOW *) malloc(sizeof(WINDOW)); /* First window */ + if (bp == NULL || wp == NULL || blistp == NULL) + exit (1); + curbp = bp; /* Make this current */ + wheadp = wp; + curwp = wp; + wp->w_wndp = NULL; /* Initialize window */ + wp->w_bufp = bp; + bp->b_nwnd = 1; /* Displayed */ + wp->w_linep = bp->b_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 = term.t_nrow - 1; /* "-1" for mode line */ + wp->w_force = 0; + wp->w_flag = WFMODE | WFHARD; /* Full */ +} + +/* + * This is the general command execution routine. It handles the fake binding + * of all the keys to "self-insert". It also clears out the "thisflag" word, + * and arranges to move it to the "lastflag", so that the next command can + * look at it. Return the status of command. + */ +int execute(int c, int f, int n) +{ + KEYTAB *ktp; + int status; + + ktp = &keytab[0]; /* Look in key table */ + while (ktp->k_fp != NULL) + { + if (ktp->k_code == c) + { + thisflag = 0; + status = (*ktp->k_fp) (f, n); + lastflag = thisflag; + return (status); + } + ++ktp; + } + + if ((c >= 0x20 && c <= 0x7E) /* Self inserting */ + || (c >= 0xA0 && c <= 0xFE)) + { + if (n <= 0) + { /* Fenceposts */ + lastflag = 0; + return (n < 0 ? FALSE : TRUE); + } + thisflag = 0; /* For the future */ + + status = linsert(n, c); + + lastflag = thisflag; + return (status); + } + mlwrite("\007[Key not bound]"); /* complain */ + lastflag = 0; /* Fake last flags */ + return (FALSE); +} + +/* + * Read in a key. Do the standard keyboard preprocessing. Convert the keys to + * the internal character set. + */ +int getkey() +{ + int c; + + c = (*term.t_getchar) (); + + if (c == METACH) + { /* Apply M- prefix */ + c = getctl (); + return (META | c); + } + if (c >= 0x00 && c <= 0x1F) /* C0 control -> C- */ + c = CTRL | (c + '@'); + return (c); +} + +/* + * Get a key. Apply control modifications to the read key. + */ +int getctl() +{ + int c; + + c = (*term.t_getchar) (); + if (c >= 'a' && c <= 'z') /* Force to upper */ + c -= 0x20; + if (c >= 0x00 && c <= 0x1F) /* C0 control -> C- */ + c = CTRL | (c + '@'); + return (c); +} + +/* + * Fancy quit command, as implemented by Norm. If any buffer has changed + * do a write on that buffer and exit emacs, otherwise simply exit. + */ +int quickexit(int f, int n) +{ + BUFFER *bp; /* scanning pointer to buffers */ + + bp = bheadp; + while (bp != NULL) + { + if ((bp->b_flag & BFCHG) != 0 /* Changed */ + && (bp->b_flag & BFTEMP) == 0) + { /* Real */ + curbp = bp; /* make that buffer current */ + mlwrite("[Saving %s]", (int*)bp->b_fname); + filesave(f, n); + } + bp = bp->b_bufp; /* on to the next buffer */ + } + return quit(f, n); /* conditionally quit */ +} + +/* + * 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". + */ +int quit(int f, int n) +{ + int s; + + if (f != FALSE /* Argument forces it */ + || anycb () == FALSE /* All buffers clean */ + || (s = mlyesno("Modified buffers exist. Leave anyway")) == TRUE) + { + vttidy(); + exit (0); + } + mlwrite(""); + return (s); +} + +/* + * Begin a keyboard macro. Error if not at the top level in keyboard + * processing. Set up variables and return. + */ +int ctlxlp(int f, int n) +{ + if (kbdmip != NULL || kbdmop != NULL) + { + mlwrite("Not now"); + return (FALSE); + } + mlwrite("[Start macro]"); + kbdmip = &kbdm[0]; + return (TRUE); +} + +/* + * End keyboard macro. Check for the same limit conditions as the above + * routine. Set up the variables and return to the caller. + */ +int ctlxrp(int f, int n) +{ + if (kbdmip == NULL) + { + mlwrite("Not now"); + return (FALSE); + } + mlwrite("[End macro]"); + kbdmip = NULL; + return (TRUE); +} + +/* + * Execute a macro. The command argument is the number of times to loop. Quit + * as soon as a command gets an error. Return TRUE if all ok, else FALSE. + */ +int ctlxe(int f, int n) +{ + int c, af, an, s; + + if (kbdmip != NULL || kbdmop != NULL) + { + mlwrite("No macro defined"); + return (FALSE); + } + if (n <= 0) + return (TRUE); + do + { + kbdmop = &kbdm[0]; + do + { + af = FALSE; + an = 1; + if ((c = *kbdmop++) == (CTRL | 'U')) + { + af = TRUE; + an = *kbdmop++; + c = *kbdmop++; + } + s = TRUE; + } + while (c != (CTLX | ')') && (s = execute(c, af, an)) == TRUE); + kbdmop = NULL; + } + while (s == TRUE && --n); + return (s); +} + +/* + * Abort. Beep the beeper. Kill off any keyboard macro, etc., that is in + * progress. Sometimes called as a routine, to do general aborting of stuff. + */ +int ctrlg(int f, int n) +{ + (*term.t_beep) (); + if (kbdmip != NULL) + { + kbdm[0] = (CTLX | ')'); + kbdmip = NULL; + } + mlwrite("[Aborted]"); + return (ABORT); +} + +/* + * Handle ANSI escape-extended commands (with "ESC [" or "ESC O" prefix) + */ +int extendedcmd(int f, int n) +{ + int (*cmd)(); + int c; + + c = getctl(); + switch (c) + { + case 'A': cmd = backline; break; + case 'B': cmd = forwline; break; + case 'C': cmd = forwchar; break; + case 'D': cmd = backchar; break; + case 'H': cmd = gotobob; break; + case 'W': cmd = gotoeob; break; + default: mlwrite("\007[Key not bound]"); + return (FALSE); + } + return cmd(f, n); +} diff --git a/src/cmd/emg/random.c b/src/cmd/emg/random.c new file mode 100644 index 0000000..39e48c8 --- /dev/null +++ b/src/cmd/emg/random.c @@ -0,0 +1,290 @@ +/* This file is in the public domain. */ + +/* + * This file contains the command processing functions for a number of random + * commands. There is no functional grouping here, for sure. + */ + +#include "estruct.h" +#include "edef.h" + +extern void mlwrite(); +extern void lchange(int flag); +extern int lnewline(); +extern int linsert(int n, int c); +extern int backchar(int f, int n); +extern void kdelete(); +extern int ldelete(int f, int n); +extern int kremove(int k); + +int setfillcol(int f, int n); +int getccol(int bflg); +int twiddle(int f, int n); +int quote(int f, int n); +int tab(int f, int n); +int openline(int f, int n); +int newline(int f, int n); +int forwdel(int f, int n); +int backdel(int f, int n); +int killtext(int f, int n); +int yank(int f, int n); + +/* + * Set fill column to n. + */ +int setfillcol(int f, int n) +{ + fillcol = n; + mlwrite("[Fill column is %d]", n); + return (TRUE); +} + +/* + * Return current column. Stop at first non-blank given TRUE argument. + */ +int getccol(int bflg) +{ + int c, i, col; + + col = 0; + for (i = 0; i < curwp->w_doto; ++i) + { + c = lgetc(curwp->w_dotp, i); + if (c != ' ' && c != '\t' && bflg) + break; + if (c == '\t') + col |= 0x07; + else if (c < 0x20 || c == 0x7F) + ++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 + */ +int twiddle(int f, int n) +{ + LINE *dotp; + int doto, cl, cr; + + dotp = curwp->w_dotp; + doto = curwp->w_doto; + if (doto == llength(dotp) && --doto < 0) + return (FALSE); + cr = lgetc(dotp, doto); + if (--doto < 0) + return (FALSE); + cl = lgetc(dotp, doto); + lputc(dotp, doto + 0, cr); + lputc(dotp, doto + 1, cl); + lchange(WFEDIT); + return (TRUE); +} + +/* + * Quote the next character, and insert it into the buffer. All the characters + * are taken literally, with the exception of the newline, which always has + * its line splitting meaning. The character is always read, even if it is + * inserted 0 times, for regularity. Bound to "C-Q" + */ +int quote(int f, int n) +{ + int s, c; + + c = (*term.t_getchar) (); + if (n < 0) + return (FALSE); + if (n == 0) + return (TRUE); + if (c == '\n') + { + do + { + s = lnewline(); + } + while (s == TRUE && --n); + return (s); + } + return (linsert(n, c)); +} + +/* + * Insert a tab into file. + * Bound to "C-I" + */ +int tab(int f, int n) +{ + if (n < 0) + return (FALSE); + return (linsert(n, 9)); +} + +/* + * Open up some blank space. The basic plan is to insert a bunch of newlines, + * and then back up over them. Everything is done by the subcommand + * processors. They even handle the looping. Normally this is bound to "C-O" + */ +int openline(int f, int n) +{ + int i, 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, n); /* of them all */ + return (s); +} + +/* + * Insert a newline. Bound to "C-M". + */ +int newline(int f, int n) +{ + int s; + + if (n < 0) + return (FALSE); + + /* insert some lines */ + while (n--) + { + if ((s = lnewline()) != TRUE) + return (s); + } + 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" + */ +int forwdel(int f, int n) +{ + if (n < 0) + return (backdel(f, -n)); + if (f != FALSE) + { /* Really a kill */ + if ((lastflag & CFKILL) == 0) + kdelete(); + thisflag |= CFKILL; + } + return (ldelete(n, f)); +} + +/* + * 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. Bound to + * both "RUBOUT" and "C-H" + */ +int backdel(int f, int n) +{ + int s; + + if (n < 0) + return (forwdel(f, -n)); + if (f != FALSE) + { /* Really a kill */ + if ((lastflag & CFKILL) == 0) + kdelete(); + thisflag |= CFKILL; + } + if ((s = backchar(f, n)) == TRUE) + s = ldelete(n, f); + return (s); +} + +/* + * Kill text. 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 + * backwards that number of newlines. Normally bound to "C-K" + */ +int killtext(int f, int n) +{ + LINE *nextp; + int chunk; + + if ((lastflag & CFKILL) == 0)/* Clear kill buffer if last wasn't a kill */ + kdelete(); + thisflag |= CFKILL; + if (f == FALSE) + { + chunk = llength(curwp->w_dotp) - curwp->w_doto; + if (chunk == 0) + chunk = 1; + } + else if (n == 0) + { + chunk = curwp->w_doto; + curwp->w_doto = 0; + } + else if (n > 0) + { + chunk = llength(curwp->w_dotp) - curwp->w_doto + 1; + nextp = lforw(curwp->w_dotp); + while (--n) + { + if (nextp == curbp->b_linep) + return (FALSE); + chunk += llength(nextp) + 1; + nextp = lforw(nextp); + } + } + else + { + mlwrite("neg kill"); + return (FALSE); + } + return (ldelete(chunk, TRUE)); +} + +/* + * Yank text back from the kill buffer. This is really easy. All of the work + * is done by the standard insert routines. All you do is run the loop, and + * check for errors. Bound to "C-Y" + */ +int yank(int f, int n) +{ + int c, i; + + if (n < 0) + return (FALSE); + while (n--) + { + i = 0; + while ((c = kremove(i)) >= 0) + { + if (c == '\n') + { + if (lnewline(FALSE, 1) == FALSE) + return (FALSE); + } + else + { + if (linsert(1, c) == FALSE) + return (FALSE); + } + ++i; + } + } + return (TRUE); +} diff --git a/src/cmd/emg/region.c b/src/cmd/emg/region.c new file mode 100644 index 0000000..1d85d32 --- /dev/null +++ b/src/cmd/emg/region.c @@ -0,0 +1,142 @@ +/* This file is in the public domain. */ + +/* + * 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 "estruct.h" +#include "edef.h" + +extern void kdelete(); +extern int ldelete(int f, int n); +extern int kinsert(int c); +extern void mlwrite(); + +int killregion(int f, int n); +int copyregion(int f, int n); +int getregion(REGION *rp); + +/* + * Kill the region. Ask "getregion" to figure out the bounds of the region. + * Move "." to the start, and kill the characters. Bound to "C-W" + */ +int killregion(int f, int n) +{ + REGION region; + int s; + + 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, TRUE)); +} + +/* + * 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. Bound to "M-W" + */ +int copyregion(int f, int n) +{ + LINE *linep; + REGION region; + int loffs, s; + + 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')) != TRUE) + return (s); + linep = lforw(linep); + loffs = 0; + } + else + { /* Middle of line */ + if ((s = kinsert(lgetc(linep, loffs))) != TRUE) + return (s); + ++loffs; + } + } + return (TRUE); +} + +/* + * This routine figures out the bounds of the region in the current window, + * and fills in the fields of the "REGION" structure pointed to by "rp". + * Because the dot and mark are usually very close together, we scan outward + * from dot looking for mark. This should save time. Return a standard code. + * Callers of this routine should be prepared to get an "ABORT" status; we + * might make this have the conform thing later + */ +int getregion(REGION *rp) +{ + LINE *flp, *blp; + int fsize, bsize; + + if (curwp->w_markp == (struct LINE*)0) + { + mlwrite("No mark set in this window"); + return (FALSE); + } + if (curwp->w_dotp == curwp->w_markp) + { + rp->r_linep = curwp->w_dotp; + if (curwp->w_doto < curwp->w_marko) + { + rp->r_offset = curwp->w_doto; + rp->r_size = curwp->w_marko - curwp->w_doto; + } + else + { + rp->r_offset = curwp->w_marko; + rp->r_size = curwp->w_doto - curwp->w_marko; + } + return (TRUE); + } + blp = curwp->w_dotp; + bsize = curwp->w_doto; + flp = curwp->w_dotp; + fsize = llength(flp) - curwp->w_doto + 1; + while (flp != curbp->b_linep || lback(blp) != curbp->b_linep) + { + if (flp != curbp->b_linep) + { + flp = lforw(flp); + if (flp == curwp->w_markp) + { + rp->r_linep = curwp->w_dotp; + rp->r_offset = curwp->w_doto; + rp->r_size = fsize + curwp->w_marko; + return (TRUE); + } + 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; + rp->r_size = bsize - curwp->w_marko; + return (TRUE); + } + } + } + mlwrite("Bug: lost mark"); + return (FALSE); +} diff --git a/src/cmd/emg/search.c b/src/cmd/emg/search.c new file mode 100644 index 0000000..8eafae8 --- /dev/null +++ b/src/cmd/emg/search.c @@ -0,0 +1,535 @@ +/* This file is in the public domain. */ + +/* + * The functions in this file implement commands that search in the forward + * and backward directions. There are no special characters in the search + * strings + */ + +#include /* strncpy(3), strncat(3) */ +#include "estruct.h" +#include "edef.h" + +extern void mlwrite(); +extern int mlreplyt(char *prompt, char *buf, int nbuf, char eolchar); +extern void update(); +extern int forwchar(int f, int n); +extern int ldelete(int n, int kflag); +extern int lnewline(); +extern int linsert(int n, int c); + +int forwsearch(int f, int n); +int forwhunt(int f, int n); +int backsearch(int f, int n); +int backhunt(int f, int n); +int bsearch(int f, int n); +int eq(int bc, int pc); +int readpattern(char *prompt); +int sreplace(int f, int n); +int qreplace(int f, int n); +int replaces(int kind, int f, int n); +int forscan(char *patrn, int leavep); +void expandp(char *srcstr, char *deststr, int maxlength); + +#define PTBEG 1 /* leave the point at the begining on search */ +#define PTEND 2 /* leave the point at the end on search */ + +/* + * Search forward. Get a search string from the user, and search, beginning at + * ".", for the string. If found, reset the "." to be just after the match + * string, and [perhaps] repaint the display. Bound to "C-S" + */ +int forwsearch(int f, int n) +{ + int status; + + if (n == 0) /* resolve the repeat count */ + n = 1; + if (n < 1) /* search backwards */ + return (backsearch(f, -n)); + + /* ask the user for the text of a pattern */ + if ((status = readpattern("Search")) != TRUE) + return (status); + + /* search for the pattern */ + while (n-- > 0) + { + if ((status = forscan(&pat[0], PTEND)) == FALSE) + break; + } + + /* and complain if not there */ + if (status == FALSE) + mlwrite("Not found"); + return (status); +} + +int forwhunt(int f, int n) +{ + int status = 0; + + /* resolve the repeat count */ + if (n == 0) + n = 1; + if (n < 1) /* search backwards */ + return (backhunt(f, -n)); + + /* Make sure a pattern exists */ + if (pat[0] == 0) + { + mlwrite("No pattern set"); + return (FALSE); + } + /* search for the pattern */ + while (n-- > 0) + { + if ((status = forscan(&pat[0], PTEND)) == FALSE) + break; + } + + /* and complain if not there */ + if (status == FALSE) + mlwrite("Not found"); + return (status); +} + +/* + * 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]. Bound to "C-R" + */ +int backsearch(int f, int n) +{ + int s; + + if (n == 0) /* resolve null and negative arguments */ + n = 1; + if (n < 1) + return (forwsearch(f, -n)); + + if ((s = readpattern("Reverse search")) != TRUE) /* get a pattern to search */ + return (s); + + return bsearch(f, n); /* and go search for it */ +} + +/* hunt backward for the last search string entered + */ +int backhunt(int f, int n) +{ + if (n == 0) /* resolve null and negative arguments */ + n = 1; + if (n < 1) + return (forwhunt(f, -n)); + + if (pat[0] == 0) /* Make sure a pattern exists */ + { + mlwrite("No pattern set"); + return (FALSE); + } + return bsearch(f, n); /* go search */ +} + +int bsearch(int f, int n) +{ + LINE *clp, *tlp; + char *epp, *pp; + int cbo, tbo, c; + + /* find a pointer to the end of the pattern */ + for (epp = &pat[0]; epp[1] != 0; ++epp) + ; + + /* make local copies of the starting location */ + clp = curwp->w_dotp; + cbo = curwp->w_doto; + + while (n-- > 0) + { + for (;;) + { + /* if we are at the begining of the line, wrap back around */ + if (cbo == 0) + { + clp = lback(clp); + + if (clp == curbp->b_linep) + { + mlwrite("Not found"); + return (FALSE); + } + cbo = llength(clp) + 1; + } + /* fake the at the end of a line */ + if (--cbo == llength(clp)) + c = '\n'; + else + c = lgetc(clp, cbo); + + /* check for a match against the end of the pattern */ + if (eq(c, *epp) != FALSE) + { + tlp = clp; + tbo = cbo; + pp = epp; + /* scanning backwards through the rest of the pattern + * looking for a match */ + while (pp != &pat[0]) + { + /* wrap across a line break */ + if (tbo == 0) + { + tlp = lback(tlp); + if (tlp == curbp->b_linep) + goto fail; + + tbo = llength(tlp) + 1; + } + /* fake the */ + if (--tbo == llength(tlp)) + c = '\n'; + else + c = lgetc(tlp, tbo); + + if (eq(c, *--pp) == FALSE) + goto fail; + } + + /* A Match! reset the current cursor */ + curwp->w_dotp = tlp; + curwp->w_doto = tbo; + curwp->w_flag |= WFMOVE; + goto next; + } + fail:; + } + next:; + } + return (TRUE); +} + +/* + * Compare two characters. The "bc" comes from the buffer. It has it's case + * folded out. The "pc" is from the pattern + */ +int eq(int bc, int pc) +{ + if (bc >= 'a' && bc <= 'z') + bc -= 0x20; + if (pc >= 'a' && pc <= 'z') + pc -= 0x20; + if (bc == pc) + return (TRUE); + 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. + */ +int readpattern(char *prompt) +{ + char tpat[NPAT + 20]; + int s; + + strncpy(tpat, prompt, NPAT-12); /* copy prompt to output string */ + strncat(tpat, " [", 3); /* build new prompt string */ + expandp(&pat[0], &tpat[strlen (tpat)], NPAT / 2); /* add old pattern */ + strncat(tpat, "]: ", 4); + + s = mlreplyt(tpat, tpat, NPAT, 10); /* Read pattern */ + + if (s == TRUE) /* Specified */ + strncpy(pat, tpat, NPAT); + else if (s == FALSE && pat[0] != 0) /* CR, but old one */ + s = TRUE; + + return (s); +} + +/* + * Search and replace (ESC-R) + */ +int sreplace(int f, int n) +{ + return (replaces(FALSE, f, n)); +} + +/* + * search and replace with query (ESC-CTRL-R) + */ +int qreplace(int f, int n) +{ + return (replaces(TRUE, f, n)); +} + +/* + * replaces: search for a string and replace it with another string. query + * might be enabled (according to kind) + */ +int replaces(int kind, int f, int n) +{ + LINE *origline; /* original "." position */ + char tmpc; /* temporary character */ + char c; /* input char for query */ + char tpat[NPAT]; /* temporary to hold search pattern */ + int i; /* loop index */ + int s; /* success flag on pattern inputs */ + int slength, rlength; /* length of search and replace strings */ + int numsub; /* number of substitutions */ + int nummatch; /* number of found matches */ + int nlflag; /* last char of search string a ? */ + int nlrepl; /* was a replace done on the last line? */ + int origoff; /* and offset (for . query option) */ + + /* check for negative repititions */ + if (f && n < 0) + return (FALSE); + + /* ask the user for the text of a pattern */ + if ((s = readpattern((kind == FALSE ? "Replace" : "Query replace"))) != TRUE) + return (s); + strncpy(&tpat[0], &pat[0], NPAT); /* salt it away */ + + /* ask for the replacement string */ + strncpy(&pat[0], &rpat[0], NPAT); /* set up default string */ + if ((s = readpattern("with")) == ABORT) + return (s); + + /* move everything to the right place and length them */ + strncpy(&rpat[0], &pat[0], NPAT); + strncpy(&pat[0], &tpat[0], NPAT); + slength = strlen(&pat[0]); + rlength = strlen(&rpat[0]); + + /* set up flags so we can make sure not to do a recursive replace on the + * last line */ + nlflag = (pat[slength - 1] == '\n'); + nlrepl = FALSE; + + /* build query replace question string */ + strncpy(tpat, "Replace '", 10); + expandp(&pat[0], &tpat[strlen (tpat)], NPAT / 3); + strncat(tpat, "' with '", 9); + + expandp(&rpat[0], &tpat[strlen (tpat)], NPAT / 3); + strncat(tpat, "'? ", 4); + + /* save original . position */ + origline = curwp->w_dotp; + origoff = curwp->w_doto; + + /* scan through the file */ + numsub = 0; + nummatch = 0; + while ((f == FALSE || n > nummatch) && + (nlflag == FALSE || nlrepl == FALSE)) + { + /* search for the pattern */ + if (forscan(&pat[0], PTBEG) != TRUE) + break; /* all done */ + ++nummatch; /* increment # of matches */ + + /* check if we are on the last line */ + nlrepl = (lforw (curwp->w_dotp) == curwp->w_bufp->b_linep); + + /* check for query */ + if (kind) + { + /* get the query */ + mlwrite(&tpat[0], &pat[0], &rpat[0]); + qprompt: + update(); /* show the proposed place to change */ + c = (*term.t_getchar) (); /* and input */ + mlwrite(""); /* and clear it */ + + /* and respond appropriately */ + switch (c) + { + case 'y': /* yes, substitute */ + case ' ': + break; + + case 'n': /* no, onword */ + forwchar(FALSE, 1); + continue; + + case '!': /* yes/stop asking */ + kind = FALSE; + break; + + case '.': /* abort! and return */ + /* restore old position */ + curwp->w_dotp = origline; + curwp->w_doto = origoff; + curwp->w_flag |= WFMOVE; + + case BELL: /* abort! and stay */ + mlwrite("Aborted!"); + return (FALSE); + + case 0x0d: /* controlled exit */ + case 'q': + return (TRUE); + + default: /* bitch and beep */ + (*term.t_beep) (); + + case '?': /* help me */ + mlwrite("(Y)es, (N)o, (!)Do the rest, (^G,RET,q)Abort, (.)Abort back, (?)Help: "); + goto qprompt; + } + } + /* delete the sucker */ + if (ldelete(slength, FALSE) != TRUE) + { + /* error while deleting */ + mlwrite("ERROR while deleting"); + return (FALSE); + } + /* and insert its replacement */ + for (i = 0; i < rlength; i++) + { + tmpc = rpat[i]; + s = (tmpc == '\n' ? lnewline() : linsert(1, tmpc)); + if (s != TRUE) + { + /* error while inserting */ + mlwrite("Out of memory while inserting"); + return (FALSE); + } + } + + numsub++; /* increment # of substitutions */ + } + + /* and report the results */ + mlwrite("%d substitutions", numsub); + return (TRUE); +} + +/* search forward for a + */ +int forscan(char *patrn, int leavep) +{ + LINE *curline; /* current line during scan */ + LINE *lastline; /* last line position during scan */ + LINE *matchline; /* current line during matching */ + char *patptr; /* pointer into pattern */ + int curoff; /* position within current line */ + int lastoff; /* position within last line */ + int c; /* character at current position */ + int matchoff; /* position in matching line */ + + /* setup local scan pointers to global "." */ + curline = curwp->w_dotp; + curoff = curwp->w_doto; + + /* scan each character until we hit the head link record */ + while (curline != curbp->b_linep) + { + /* save the current position in case we need to restore it on a match */ + lastline = curline; + lastoff = curoff; + + /* get the current character resolving EOLs */ + if (curoff == llength(curline)) + { /* if at EOL */ + curline = lforw(curline); /* skip to next line */ + curoff = 0; + c = '\n'; /* and return a */ + } + else + c = lgetc(curline, curoff++); /* get the char */ + + /* test it against first char in pattern */ + if (eq(c, patrn[0]) != FALSE) /* if we find it. */ + { + /* setup match pointers */ + matchline = curline; + matchoff = curoff; + patptr = &patrn[0]; + + /* scan through patrn for a match */ + while (*++patptr != 0) + { + /* advance all the pointers */ + if (matchoff == llength(matchline)) + { + /* advance past EOL */ + matchline = lforw(matchline); + matchoff = 0; + c = '\n'; + } + else + c = lgetc(matchline, matchoff++); + + /* and test it against the pattern */ + if (eq(*patptr, c) == FALSE) + goto fail; + } + + /* A SUCCESSFULL MATCH!!! */ + /* reset the global "." pointers */ + if (leavep == PTEND) + { /* at end of string */ + curwp->w_dotp = matchline; + curwp->w_doto = matchoff; + } + else + { /* at begining of string */ + curwp->w_dotp = lastline; + curwp->w_doto = lastoff; + } + curwp->w_flag |= WFMOVE; /* flag that we have moved */ + return (TRUE); + } + fail:; /* continue to search */ + } + /* we could not find a match */ + return (FALSE); +} + +/* expandp: expand control key sequences for output + */ +void expandp(char *srcstr, char *deststr, int maxlength) +{ + char c; /* current char to translate */ + + /* scan through the string */ + while ((c = *srcstr++) != 0) + { + if (c < 0x20 || c == 0x7f) + { /* control character */ + *deststr++ = '^'; + *deststr++ = c ^ 0x40; + maxlength -= 2; + } + else if (c == '%') + { + *deststr++ = '%'; + *deststr++ = '%'; + maxlength -= 2; + } + else + { /* any other character */ + *deststr++ = c; + maxlength--; + } + + /* check for maxlength */ + if (maxlength < 4) + { + *deststr++ = '$'; + *deststr = '\0'; + return; + } + } + *deststr = '\0'; + return; +} diff --git a/src/cmd/emg/tcap.c b/src/cmd/emg/tcap.c new file mode 100644 index 0000000..3bf4ef6 --- /dev/null +++ b/src/cmd/emg/tcap.c @@ -0,0 +1,144 @@ +/* This file is in the public domain. */ + +/* termios video driver */ + +#define termdef 1 /* don't define "term" externally */ + +/* Did you remember to set FORCE_COLS? */ +#ifndef FORCE_COLS +#define FORCE_COLS 80 +#endif + +/* Did you remember to set FORCE_ROWS? */ +#ifndef FORCE_ROWS +#define FORCE_ROWS 24 +#endif + +/* + * XXX: + * Default/sane(?) maximum column and row sizes. + * Taken from mg1a. + * + * Let the user override this with a + * CFLAGS += -DMAXCOL=XXX -DMAXROW=XXX + * line in the Makefile. + */ +#ifndef MAXCOL +#define MAXCOL 132 +#endif + +#ifndef MAXROW +#define MAXROW 66 +#endif + +#include /* puts(3), snprintf(3) */ +#include "estruct.h" +#include "edef.h" +#undef CTRL /* Needs to be done here. */ +#include + +extern int tgetent(); +extern char *tgetstr(); +extern char *tgoto(); +extern void tputs(); + +extern char *getenv(); +extern void ttopen(); +extern int ttgetc(); +extern void ttputc(); +extern void ttflush(); +extern void ttclose(); + +extern void panic(); + +void getwinsize(); +void tcapopen(); +void tcapmove(int row, int col); +void tcapeeol(); +void tcapeeop(); +void tcaprev(); +void tcapbeep(); + +#define MARGIN 8 +#define SCRSIZ 64 +#define BEL 0x07 +#define TCAPSLEN 64 + +char tcapbuf[TCAPSLEN]; /* capabilities actually used */ +char *CM, *CE, *CL, *SO, *SE; + +TERM term = { + 0, 0, MARGIN, SCRSIZ, tcapopen, ttclose, ttgetc, ttputc, + ttflush, tcapmove, tcapeeol, tcapeeop, tcapbeep, tcaprev +}; + +void getwinsize() +{ + int cols = FORCE_COLS; + int rows = FORCE_ROWS; + + /* Too small and we're out */ + if ((cols < 10) || (rows < 3)) + panic("Too few columns or rows"); + + if (FORCE_COLS > MAXCOL) + cols = MAXCOL; + if (FORCE_ROWS > MAXROW) + rows = MAXROW; + + term.t_ncol = cols; + term.t_nrow = rows - 1; +} + +void tcapopen() +{ + char tcbuf[1024]; + char *p, *tv_stype; + + if ((tv_stype = getenv("TERM")) == NULL) + panic("TERM not defined"); + if ((tgetent(tcbuf, tv_stype)) != 1) + panic("Unknown terminal type"); + p = tcapbuf; + CL = tgetstr("cl", &p); + CM = tgetstr("cm", &p); + CE = tgetstr("ce", &p); + SE = tgetstr("se", &p); + SO = tgetstr("so", &p); + + if (CE == NULL) + eolexist = FALSE; + if (SO != NULL && SE != NULL) + revexist = TRUE; + if (CL == NULL || CM == NULL) + panic("Need cl & cm abilities"); + if (p >= &tcapbuf[TCAPSLEN]) /* XXX */ + panic("Description too big"); + ttopen (); +} + +void tcaprev(int state) +{ + if (revexist) + tputs((state ? SO : SE), 1, ttputc); +} + +void tcapmove (int row, int col) +{ + tputs(tgoto(CM, col, row), 1, ttputc); +} + +void tcapeeol() +{ + tputs(CE, 1, ttputc); +} + +void tcapeeop() +{ + tputs(CL, 1, ttputc); +} + +void tcapbeep() +{ + ttputc(BEL); +} diff --git a/src/cmd/emg/ttyio.c b/src/cmd/emg/ttyio.c new file mode 100644 index 0000000..f94e26e --- /dev/null +++ b/src/cmd/emg/ttyio.c @@ -0,0 +1,163 @@ +/* This file is in the public domain. */ + +/* + * This file comes from mg1a. + * Uses the panic function from OpenBSD's mg. + */ + +/* + * Ultrix-32 and Unix terminal I/O. + * The functions in this file + * negotiate with the operating system for + * keyboard characters, and write characters to + * the display in a barely buffered fashion. + */ + +#include +#include +#include +#include +#include +#undef CTRL +#include "estruct.h" + +void ttflush(void); +void panic(char *); + +extern void getwinsize(); + +#define NROW 66 /* Rows. */ +#define NCOL 132 /* Columns. */ +#define NOBUF 512 /* Output buffer size. */ + +char obuf[NOBUF]; /* Output buffer. */ +int nobuf; +struct sgttyb oldtty; /* V6/V7 stty data. */ +struct sgttyb newtty; +struct tchars oldtchars; /* V7 editing. */ +struct tchars newtchars; +struct ltchars oldltchars; /* 4.2 BSD editing. */ +struct ltchars newltchars; + +/* + * This function gets called once, to set up + * the terminal channel. + */ +void ttopen(void) { + register char *tv_stype; + char *getenv(), *tgetstr(), tcbuf[1024]; + + if (ioctl(0, TIOCGETP, (char *) &oldtty) < 0) + panic("ttopen can't get sgtty"); + newtty.sg_ospeed = oldtty.sg_ospeed; + newtty.sg_ispeed = oldtty.sg_ispeed; + newtty.sg_erase = oldtty.sg_erase; + newtty.sg_kill = oldtty.sg_kill; + newtty.sg_flags = oldtty.sg_flags; + newtty.sg_flags &= ~(ECHO|CRMOD); /* Kill echo, CR=>NL. */ + newtty.sg_flags |= RAW|ANYP; /* raw mode for 8 bit path.*/ + if (ioctl(0, TIOCSETP, (char *) &newtty) < 0) + panic("ttopen can't set sgtty"); + if (ioctl(0, TIOCGETC, (char *) &oldtchars) < 0) + panic("ttopen can't get chars"); + newtchars.t_intrc = 0xFF; /* Interrupt. */ + newtchars.t_quitc = 0xFF; /* Quit. */ + newtchars.t_startc = 0xFF; /* ^Q, for terminal. */ + newtchars.t_stopc = 0xFF; /* ^S, for terminal. */ + newtchars.t_eofc = 0xFF; + newtchars.t_brkc = 0xFF; + if (ioctl(0, TIOCSETC, (char *) &newtchars) < 0) + panic("ttopen can't set chars"); + if (ioctl(0, TIOCGLTC, (char *) &oldltchars) < 0) + panic("ttopen can't get ltchars"); + newltchars.t_suspc = 0xFF; /* Suspend #1. */ + newltchars.t_dsuspc = 0xFF; /* Suspend #2. */ + newltchars.t_rprntc = 0xFF; + newltchars.t_flushc = 0xFF; /* Output flush. */ + newltchars.t_werasc = 0xFF; + newltchars.t_lnextc = 0xFF; /* Literal next. */ + if (ioctl(0, TIOCSLTC, (char *) &newltchars) < 0) + panic("ttopen can't set ltchars"); + +/* do this the REAL way */ + if ((tv_stype = getenv("TERM")) == NULL) + panic("TERM not defined"); + + if((tgetent(tcbuf, tv_stype)) != 1) + panic("Unknown terminal type"); + + getwinsize(); +} + +/* + * This function gets called just + * before we go back home to the shell. Put all of + * the terminal parameters back. + */ +void ttclose(void) { + ttflush(); + if (ioctl(0, TIOCSLTC, (char *) &oldltchars) < 0) + panic("ttclose can't set ltchars"); + if (ioctl(0, TIOCSETC, (char *) &oldtchars) < 0) + panic("ttclose can't set chars"); + if (ioctl(0, TIOCSETP, (char *) &oldtty) < 0) + panic("ttclose can't set sgtty"); +} + +/* + * Write character to the display. + * Characters are buffered up, to make things + * a little bit more efficient. + */ +int ttputc(int c) { + if (nobuf >= NOBUF) + ttflush(); + obuf[nobuf++] = c; + return (c); +} + +/* + * Flush output. + */ +void ttflush(void) { + if (nobuf != 0) { + if (write(1, obuf, nobuf) != nobuf) + panic("ttflush write failed"); + nobuf = 0; + } +} + +/* + * Read character from terminal. + * All 8 bits are returned, so that you can use + * a multi-national terminal. + */ +int ttgetc(void) { + char buf[1]; + + while (read(0, &buf[0], 1) != 1); + return (buf[0] & 0xFF); +} + +/* + * typeahead returns TRUE if there are characters available to be read + * in. + */ +int typeahead(void) { + int x; + + return((ioctl(0, FIONREAD, (char *) &x) < 0) ? 0 : x); +} + +/* + * panic - just exit, as quickly as we can. + * From OpenBSD's mg. + */ +void panic(char *s) +{ + ttclose(); + (void) fputs("panic: ", stderr); + (void) fputs(s, stderr); + (void) fputc('\n', stderr); + exit(1); +} diff --git a/src/cmd/emg/window.c b/src/cmd/emg/window.c new file mode 100644 index 0000000..a1e6947 --- /dev/null +++ b/src/cmd/emg/window.c @@ -0,0 +1,336 @@ +/* This file is in the public domain. */ + +/* + * Window management. Some of the functions are internal, and some are + * attached to keys that the user actually types + */ + +#include /* free(3), malloc(3) */ +#include "estruct.h" +#include "edef.h" + +extern void upmode(); +extern void mlwrite(); + +int refresh(int f, int n); +int nextwind(int f, int n); +int prevwind(int f, int n); +int onlywind(int f, int n); +int splitwind(int f, int n); +int enlargewind(int f, int n); +int shrinkwind(int f, int n); +WINDOW* wpopup(); + +/* + * Refresh the screen. With no argument, it does the refresh and centers + * the cursor on the screen. With an argument it does a reposition instead. + * Bound to "C-L" + */ +int refresh(int f, int n) +{ + if (n >= 0) + n++; /* adjust to screen row */ + if (f == FALSE) + { + sgarbf = TRUE; + n = 0; /* Center dot */ + } + curwp->w_force = n; + curwp->w_flag |= WFFORCE; + return (TRUE); +} + +/* + * The command 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. Bound to "C-X C-N" + */ +int nextwind(int f, int n) +{ + WINDOW *wp; + + if ((wp = curwp->w_wndp) == NULL) + wp = wheadp; + + curwp = wp; + curbp = wp->w_bufp; + upmode(); + return (TRUE); +} + +/* + * 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 + */ +int prevwind(int f, int n) +{ + WINDOW *wp1, *wp2; + + wp1 = wheadp; + wp2 = curwp; + + if (wp1 == wp2) + wp2 = NULL; + + while (wp1->w_wndp != wp2) + wp1 = wp1->w_wndp; + + curwp = wp1; + curbp = wp1->w_bufp; + upmode(); + return (TRUE); +} + +/* + * This command makes the current window the only window on the screen. Bound + * to "C-X 1". 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 + */ +int onlywind(int f, int n) +{ + WINDOW *wp; + LINE *lp; + int i; + + while (wheadp != curwp) + { + wp = wheadp; + wheadp = wp->w_wndp; + if (--wp->w_bufp->b_nwnd == 0) + { + wp->w_bufp->b_dotp = wp->w_dotp; + wp->w_bufp->b_doto = wp->w_doto; + wp->w_bufp->b_markp = wp->w_markp; + wp->w_bufp->b_marko = wp->w_marko; + } + free((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 = term.t_nrow - 1; + 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. Bound to "C-X 2" + */ +int splitwind(int f, int n) +{ + LINE *lp; + WINDOW *wp, *wp1, *wp2; + int ntru, ntrl, ntrd; + + if (curwp->w_ntrows < 3) + { + mlwrite("Cannot split a %d line window", curwp->w_ntrows); + return (FALSE); + } + if ((wp = (WINDOW *) malloc(sizeof(WINDOW))) == NULL) + { + mlwrite("Cannot allocate WINDOW block"); + 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. Bound to "C-X Z" + */ +int enlargewind(int f, int n) +{ + WINDOW *adjwp; + LINE *lp; + int i; + + if (n < 0) + return (shrinkwind(f, -n)); + if (wheadp->w_wndp == NULL) + { + mlwrite("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) + { + mlwrite("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 + */ +int shrinkwind(int f, int n) +{ + WINDOW *adjwp; + LINE *lp; + int i; + + if (n < 0) + return (enlargewind(f, -n)); + if (wheadp->w_wndp == NULL) + { + mlwrite("Only one window"); + return (FALSE); + } + if ((adjwp = curwp->w_wndp) == NULL) + { + adjwp = wheadp; + while (adjwp->w_wndp != curwp) + adjwp = adjwp->w_wndp; + } + if (curwp->w_ntrows <= n) + { + mlwrite("Impossible change"); + return (FALSE); + } + 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); +} + +/* + * 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() +{ + WINDOW *wp; + + if (wheadp->w_wndp == NULL /* Only 1 window */ + && splitwind(FALSE, 0) == FALSE) /* and it won't split */ + return (NULL); + wp = wheadp; /* Find window to use */ + while (wp != NULL && wp == curwp) + wp = wp->w_wndp; + return (wp); +} diff --git a/src/cmd/emg/word.c b/src/cmd/emg/word.c new file mode 100644 index 0000000..abdc058 --- /dev/null +++ b/src/cmd/emg/word.c @@ -0,0 +1,284 @@ +/* This file is in the public domain. */ + +/* + * The routines in this file implement commands that work word at a time. + * There are all sorts of word mode commands. If I do any sentence and/or + * paragraph mode commands, they are likely to be put in this file + */ + +#include "estruct.h" +#include "edef.h" + +extern int backchar(int f, int n); +extern int forwchar(int f, int n); +extern void lchange(int flag); +extern int ldelete(int n, int kflag); +extern void mlwrite(); +extern int linsert(int n, int c); +extern int lnewline(); + +int backword(int f, int n); +int forwword(int f, int n); +int upperword(int f, int n); +int lowerword(int f, int n); +int capword(int f, int n); +int delfword(int f, int n); +int delbword(int f, int n); +int inword(); + +/* + * Move the cursor backward by "n" words. All of the details of motion are + * performed by the "backchar" and "forwchar" routines. Error if you try to + * move beyond the buffers + */ +int backword(int f, int n) +{ + if (n < 0) + return (forwword(f, -n)); + if (backchar(FALSE, 1) == FALSE) + return (FALSE); + while (n--) + { + while (inword() == FALSE) + { + if (backchar(FALSE, 1) == FALSE) + return (FALSE); + } + while (inword() != FALSE) + { + if (backchar(FALSE, 1) == FALSE) + return (FALSE); + } + } + return (forwchar(FALSE, 1)); +} + +/* + * Move the cursor forward by the specified number of words. All of the motion + * is done by "forwchar". Error if you try and move beyond the buffer's end + */ +int forwword(int f, int n) +{ + if (n < 0) + return (backword(f, -n)); + while (n--) + { + while (inword() != FALSE) + { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + while (inword() == FALSE) + { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + } + return (TRUE); +} + +/* + * Move the cursor forward by the specified number of words. As you move, + * convert any characters to upper case. Error if you try and move beyond the + * end of the buffer. Bound to "M-U" + */ +int upperword(int f, int n) +{ + int c; + + if (n < 0) + return (FALSE); + while (n--) + { + while (inword() == FALSE) + { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + while (inword() != FALSE) + { + c = lgetc(curwp->w_dotp, curwp->w_doto); + if (c >= 'a' && c <= 'z') + { + c -= 'a' - 'A'; + lputc(curwp->w_dotp, curwp->w_doto, c); + lchange(WFHARD); + } + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + } + return (TRUE); +} + +/* + * Move the cursor forward by the specified number of words. As you move + * convert characters to lower case. Error if you try and move over the end of + * the buffer. Bound to "M-L" + */ +int lowerword(int f, int n) +{ + int c; + + if (n < 0) + return (FALSE); + while (n--) + { + while (inword() == FALSE) + { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + while (inword() != FALSE) + { + c = lgetc(curwp->w_dotp, curwp->w_doto); + if (c >= 'A' && c <= 'Z') + { + c += 'a' - 'A'; + lputc(curwp->w_dotp, curwp->w_doto, c); + lchange(WFHARD); + } + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + } + 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. Bound to "M-C" + */ +int capword(int f, int n) +{ + int c; + + if (n < 0) + return(FALSE); + while (n--) + { + while (inword() == FALSE) + { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + if (inword() != FALSE) + { + c = lgetc(curwp->w_dotp, curwp->w_doto); + if (c >= 'a' && c <= 'z') + { + c -= 'a' - 'A'; + lputc(curwp->w_dotp, curwp->w_doto, c); + lchange(WFHARD); + } + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + while (inword() != FALSE) + { + c = lgetc(curwp->w_dotp, curwp->w_doto); + if (c >= 'A' && c <= 'Z') + { + c += 'a' - 'A'; + lputc(curwp->w_dotp, curwp->w_doto, c); + lchange(WFHARD); + } + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + } + } + } + return (TRUE); +} + +/* + * Kill forward by "n" words. Remember the location of dot. Move forward by + * the right number of words. Put dot back where it was and issue the kill + * command for the right number of characters. Bound to "M-D" + */ +int delfword(int f, int n) +{ + LINE *dotp; + int size, doto; + + if (n < 0) + return (FALSE); + dotp = curwp->w_dotp; + doto = curwp->w_doto; + size = 0; + while (n--) + { + while (inword() != FALSE) + { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + ++size; + } + while (inword() == FALSE) + { + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + ++size; + } + } + curwp->w_dotp = dotp; + curwp->w_doto = doto; + return (ldelete(size, TRUE)); +} + +/* + * Kill backwards by "n" words. Move backwards by the desired number of words, + * counting the characters. When dot is finally moved to its resting place, + * fire off the kill command. Bound to "M-Rubout" and to "M-Backspace" + */ +int delbword(int f, int n) +{ + int size; + + if (n < 0) + return (FALSE); + if (backchar(FALSE, 1) == FALSE) + return (FALSE); + size = 0; + while (n--) + { + while (inword() == FALSE) + { + if (backchar(FALSE, 1) == FALSE) + return (FALSE); + ++size; + } + while (inword() != FALSE) + { + if (backchar(FALSE, 1) == FALSE) + return (FALSE); + ++size; + } + } + if (forwchar(FALSE, 1) == FALSE) + return (FALSE); + return (ldelete(size, TRUE)); +} + +/* + * 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 + */ +int inword() +{ + int c; + + if (curwp->w_doto == llength(curwp->w_dotp)) + return (FALSE); + c = lgetc(curwp->w_dotp, curwp->w_doto); + if (c >= 'a' && c <= 'z') + return (TRUE); + if (c >= 'A' && c <= 'Z') + return (TRUE); + if (c >= '0' && c <= '9') + return (TRUE); + if (c == '$' || c == '_') /* For identifiers */ + return (TRUE); + return (FALSE); +}