├── README.md ├── ChangeLog ├── README.original ├── ebind.h ├── Makefile ├── termio.c ├── edef.h ├── fileio.c ├── tcap.c ├── ee.1 ├── region.c ├── efunc.h ├── ERSATZ.keys ├── estruct.h ├── random.c ├── window.c ├── word.c ├── basic.c ├── main.c ├── file.c ├── buffer.c ├── line.c ├── search.c └── display.c /README.md: -------------------------------------------------------------------------------- 1 | ersatz-emacs 2 | ============ 3 | 4 | The smallest emacs-like editor I could find -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2004-01-10 Chris Baird 2 | 3 | * beginning of lint-ification. 4 | Start work on making 64-bit clean. 5 | 6 | 2001-01-09 Chris Baird 7 | 8 | * removed unused NBIND/names[] definitions. 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.original: -------------------------------------------------------------------------------- 1 | This shar file contains the source to a microemacs-derived text editor 2 | that I have been personally hacking on for over a decade. 3 | 4 | Originally this was MicroEMACS 3.6 as released to mod.sources and the 5 | Public Domain by Daniel Lawrence in 1986, and was itself based on the 6 | work of Steve Wilhite and George Jones to MicroEMACS 2.0 (then also 7 | public domain) by Dave Conroy. I would like to reiterate Lawrence's 8 | thanks to them for writing such nice, well structured and documented 9 | code. 10 | 11 | "Ersatz-Emacs", as I call it today, is the above text editor throughly 12 | cleansed of routines and features that I personally never use. It is 13 | also an editor MINIX-creator Andy Tanenbaum could describe as "fitting 14 | inside a student's brain" (namely, mine). 15 | 16 | This source code should compile cleanly on any "modern" UN*X system 17 | with a termcap/curses library. This release has been tested with 18 | NetBSD and various Linux systems, although in the past when it was 19 | still mostly MicroEMACS, proto-Ersatz-Emacs was an editor of choice on 20 | SunOS, Solaris, Xenix, Minix/i386, and AIX. Supporting these and 21 | similar systems should not be difficult. 22 | 23 | I encourage people to personalise this very simple editor to their own 24 | requirements. Please send any useful bug reports and fixes back to me, 25 | but I'm not really interested in incorporating new features unless it 26 | simplifies the program further. Feel free to do a code-fork and 27 | distribute your own perfect text editor. 28 | 29 | The title "Ersatz" comes from the category Richard Stallman uses in 30 | MIT AI Memo 519a to describe those editors that are a surface-deep 31 | imitation (key bindings) of "real" ITS Emacs. If you are familiar with 32 | any Emacs-variant editor, you should have few problems with Ersatz. 33 | 34 | All source code of this program is in the Public Domain. I am a rabid 35 | Stallmanite weenie, but it would be improper to publish this under a 36 | different licence than it was given to me with. 37 | 38 | -- 39 | Chris Baird,, 40 | -------------------------------------------------------------------------------- /ebind.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This table is *roughly* in ASCII order, left to right across the 3 | * characters of the command. This expains the funny location of the 4 | * control-X commands. 5 | */ 6 | 7 | KEYTAB keytab[] = { 8 | {CTRL | '@', setmark}, 9 | {CTRL | 'A', gotobol}, 10 | {CTRL | 'B', backchar}, 11 | {CTRL | 'D', forwdel}, 12 | {CTRL | 'E', gotoeol}, 13 | {CTRL | 'F', forwchar}, 14 | {CTRL | 'G', ctrlg}, 15 | {CTRL | 'H', backdel}, 16 | {CTRL | 'I', tab}, 17 | {CTRL | 'K', killtext}, 18 | {CTRL | 'L', refresh}, 19 | {CTRL | 'M', newline}, 20 | {CTRL | 'N', forwline}, 21 | {CTRL | 'O', openline}, 22 | {CTRL | 'P', backline}, 23 | {CTRL | 'Q', quote}, 24 | {CTRL | 'R', backsearch}, 25 | {CTRL | 'S', forwsearch}, 26 | {CTRL | 'T', twiddle}, 27 | {CTRL | 'V', forwpage}, 28 | {CTRL | 'W', killregion}, 29 | {CTRL | 'Y', yank}, 30 | {CTLX | '(', ctlxlp}, 31 | {CTLX | ')', ctlxrp}, 32 | {CTLX | '1', onlywind}, 33 | {CTLX | '2', splitwind}, 34 | {CTLX | '=', showcpos}, 35 | {CTLX | 'B', usebuffer}, 36 | {CTLX | 'E', ctlxe}, 37 | {CTLX | 'F', setfillcol}, 38 | {CTLX | 'K', killbuffer}, 39 | {CTLX | 'N', filename}, 40 | {CTLX | 'O', nextwind}, 41 | {CTLX | 'X', nextbuffer}, 42 | {CTLX | '^', enlargewind}, 43 | {CTLX | CTRL | 'B', listbuffers}, 44 | {CTLX | CTRL | 'C', quit}, 45 | {CTLX | CTRL | 'F', filefind}, 46 | {CTLX | CTRL | 'I', insfile}, 47 | {CTLX | CTRL | 'R', fileread}, 48 | {CTLX | CTRL | 'S', filesave}, 49 | {CTLX | CTRL | 'W', filewrite}, 50 | {META | ' ', setmark}, 51 | {META | '%', qreplace}, 52 | {META | '.', setmark}, 53 | {META | '<', gotobob}, 54 | {META | '>', gotoeob}, 55 | {META | 'B', backword}, 56 | {META | 'C', capword}, 57 | {META | 'D', delfword}, 58 | {META | 'F', forwword}, 59 | {META | 'G', gotoline}, /* non-standard */ 60 | {META | 'L', lowerword}, 61 | {META | 'N', gotoeop}, 62 | {META | 'P', gotobop}, 63 | {META | 'Q', fillpara}, 64 | {META | 'R', sreplace}, 65 | {META | 'U', upperword}, 66 | {META | 'V', backpage}, 67 | {META | 'W', copyregion}, 68 | {META | 'Z', quickexit}, 69 | {META | 0x7F, delbword}, 70 | {META | CTRL | 'H', delbword}, 71 | {META | CTRL | 'N', namebuffer}, 72 | {META | CTRL | 'V', scrnextdw}, 73 | {META | CTRL | 'Z', scrnextup}, /* non-standard */ 74 | {0x7F, backdel}, 75 | {META | '[', extendedcmd}, 76 | {META | 'O', extendedcmd}, 77 | {0, 0} 78 | }; 79 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # The name to install the editor and documentation under. Long-time microemacs 2 | # users might prefer "me" or "uemacs" instead. 3 | EXEC=ee 4 | 5 | CFLAGS=-Wall -O2 6 | # To be /really/ fanatical on the executible's size... 7 | #CFLAGS=-Os -fomit-frame-pointer -malign-loops=0 -malign-jumps=0 -malign-functions=0 -Wall 8 | 9 | # Uncomment to force the screen size, in situtations were it is desirable or 10 | # if your system lacks the TIOCGWINSZ ioctl. 11 | #DUMBCONS= -DFORCE_COLS=80 -DFORCE_ROWS=24 12 | 13 | # NetBSD, Linux, and nearly all other POSIX-based enviroments 14 | LFLAGS= -ltermcap 15 | # Try this on some older systems 16 | #LFLAGS= -lcurses 17 | # For very early Linux 18 | #LFLAGS= -lbsd 19 | 20 | # If you should change DOCDIR, be certain to update the path mentioned 21 | # in the man page. 22 | PREFIX= /usr/local 23 | DOCDIR= ${PREFIX}/share 24 | BINDIR= ${PREFIX}/bin 25 | MANDIR= ${PREFIX}/man/man1 26 | 27 | EXTRA= README Makefile ERSATZ.keys ee.1 ChangeLog 28 | 29 | HFILES= estruct.h edef.h efunc.h ebind.h 30 | 31 | CFILES= basic.c buffer.c display.c file.c fileio.c line.c main.c \ 32 | random.c region.c search.c tcap.c termio.c window.c word.c 33 | 34 | OFILES= basic.o buffer.o display.o file.o fileio.o line.o main.o \ 35 | random.o region.o search.o tcap.o termio.o window.o word.o 36 | 37 | all: $(OFILES) 38 | $(CC) $(CFLAGS) $(OFILES) -o $(EXEC) $(LFLAGS) 39 | 40 | install: all 41 | install -c -m 0755 -o bin -g bin -s $(EXEC) $(BINDIR) 42 | install -c -m 0444 -o bin -g bin ERSATZ.keys $(DOCDIR) 43 | install -c -m 0644 -o bin -g bin ee.1 $(MANDIR)/$(EXEC).1 44 | 45 | clean: 46 | rm -f *.o *~ *.core *.bak ersatz.shar $(EXEC) 47 | 48 | backup: 49 | -mv -f backup01.tar.gz backup02.tar.gz 50 | -mv -f backup00.tar.gz backup01.tar.gz 51 | tar zcf backup00.tar.gz $(CFILES) $(HFILES) $(EXTRA) 52 | 53 | shar: clean 54 | shar $(EXTRA) $(HFILES) $(CFILES) >ersatz.shar 55 | 56 | floppy: 57 | tar cvf /dev/fd0a $(EXTRA) $(CFILES) $(HFILES) 58 | 59 | basic.o: basic.c estruct.h edef.h 60 | buffer.o: buffer.c estruct.h edef.h 61 | display.o: display.c estruct.h edef.h 62 | file.o: file.c estruct.h edef.h 63 | fileio.o: fileio.c estruct.h 64 | line.o: line.c estruct.h edef.h 65 | main.o: main.c estruct.h edef.h efunc.h ebind.h 66 | random.o: random.c estruct.h edef.h 67 | region.o: region.c estruct.h edef.h 68 | search.o: search.c estruct.h edef.h 69 | tcap.o: tcap.c estruct.h edef.h 70 | $(CC) $(CFLAGS) $(DUMBCONS) -c $< 71 | termio.o: termio.c 72 | window.o: window.c estruct.h edef.h 73 | word.o: word.c estruct.h edef.h 74 | -------------------------------------------------------------------------------- /termio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The functions in this file negotiate with the operating system for 3 | * characters, and write characters in a barely buffered fashion on the 4 | * display 5 | */ 6 | 7 | #undef CTRL 8 | #include 9 | #include 10 | #include 11 | #include /* puts(3), setbuffer(3), ... */ 12 | #include /* to get at the typeahead */ 13 | 14 | void ttopen (); 15 | void ttclose (); 16 | void ttputc (int c); 17 | void ttflush (); 18 | int ttgetc (); 19 | int typahead (); 20 | 21 | #define TBUFSIZ 128 22 | char tobuf[TBUFSIZ]; /* terminal output buffer */ 23 | struct termios ostate, nstate; 24 | 25 | /* 26 | * This function is called once to set up the terminal device streams. 27 | */ 28 | void ttopen () 29 | { 30 | /* save terminal flags */ 31 | if ((tcgetattr(0, &ostate) < 0) || (tcgetattr(0, &nstate) < 0)) 32 | { 33 | puts ("Can't read terminal capabilites\n"); 34 | exit (1); 35 | } 36 | cfmakeraw(&nstate); /* set raw mode */ 37 | nstate.c_cc[VMIN] = 1; 38 | nstate.c_cc[VTIME] = 0; /* block indefinitely for a single char */ 39 | if (tcsetattr(0, TCSADRAIN, &nstate) < 0) 40 | { 41 | puts ("Can't set terminal mode\n"); 42 | exit (1); 43 | } 44 | /* provide a smaller terminal output buffer so that the type ahead 45 | * detection works better (more often) */ 46 | setbuffer (stdout, &tobuf[0], TBUFSIZ); 47 | signal (SIGTSTP, SIG_DFL); 48 | } 49 | 50 | /* 51 | * This function gets called just before we go back home to the command 52 | * interpreter 53 | */ 54 | void ttclose () 55 | { 56 | ttflush (); 57 | if (tcsetattr(0, TCSADRAIN, &ostate) < 0) 58 | { 59 | puts ("Can't restore terminal flags"); 60 | exit (1); 61 | } 62 | } 63 | 64 | /* 65 | * Write a character to the display 66 | */ 67 | void ttputc (int c) 68 | { 69 | fputc (c, stdout); 70 | } 71 | 72 | /* 73 | * Flush terminal buffer. Does real work where the terminal output is buffered 74 | * up. A no-operation on systems where byte at a time terminal I/O is done 75 | */ 76 | void ttflush () 77 | { 78 | tcdrain (0); 79 | fflush (stdout); 80 | } 81 | 82 | /* 83 | * Read a character from the terminal, performing no editing and doing no echo 84 | * at all 85 | */ 86 | int ttgetc () 87 | { 88 | return (127 & fgetc (stdin)); 89 | } 90 | 91 | /* typahead: Check to see if any characters are already in the keyboard buffer 92 | */ 93 | int typahead () 94 | { 95 | int x; /* holds # of pending chars */ 96 | return ((ioctl (0, FIONREAD, &x) < 0) ? 0 : x); 97 | } 98 | -------------------------------------------------------------------------------- /edef.h: -------------------------------------------------------------------------------- 1 | #ifndef NULL 2 | #define NULL ((void*)0) 3 | #endif 4 | 5 | #ifdef maindef 6 | /* 7 | * for MAIN.C 8 | * initialized global definitions 9 | */ 10 | 11 | short kbdm[NKBDM] = {CTLX | ')'}; /* Macro */ 12 | int fillcol = 72; /* Current fill column */ 13 | char pat[NPAT]; /* Search pattern */ 14 | char rpat[NPAT]; /* replacement pattern */ 15 | int revexist = FALSE; 16 | int eolexist = TRUE; /* does clear to EOL exist */ 17 | int sgarbf = TRUE; /* TRUE if screen is garbage */ 18 | int mpresf = FALSE; /* TRUE if message in last line */ 19 | 20 | /* uninitialized global definitions */ 21 | 22 | int currow; /* Cursor row */ 23 | int curcol; /* Cursor column */ 24 | int thisflag; /* Flags, this command */ 25 | int lastflag; /* Flags, last command */ 26 | int curgoal; /* Goal for C-P, C-N */ 27 | WINDOW *curwp; /* Current window */ 28 | BUFFER *curbp; /* Current buffer */ 29 | WINDOW *wheadp; /* Head of list of windows */ 30 | BUFFER *bheadp; /* Head of list of buffers */ 31 | BUFFER *blistp; /* Buffer for C-X C-B */ 32 | short *kbdmip; /* Input pointer for above */ 33 | short *kbdmop; /* Output pointer for above */ 34 | 35 | #else 36 | /* 37 | * for all the other .C files 38 | * initialized global external declarations 39 | */ 40 | 41 | extern int fillcol; /* Fill column */ 42 | extern short kbdm[]; /* Holds kayboard macro data */ 43 | extern char pat[]; /* Search pattern */ 44 | extern char rpat[]; /* Replacement pattern */ 45 | extern int eolexist; /* does clear to EOL exist? */ 46 | extern int revexist; /* does reverse video exist? */ 47 | extern char *modename[]; /* text names of modes */ 48 | extern char modecode[]; /* letters to represent modes */ 49 | extern KEYTAB keytab[]; /* key bind to functions table */ 50 | extern int gmode; /* global editor mode */ 51 | extern int sgarbf; /* State of screen unknown */ 52 | extern int mpresf; /* Stuff in message line */ 53 | extern int clexec; /* command line execution flag */ 54 | 55 | /* initialized global external declarations */ 56 | 57 | extern int currow; /* Cursor row */ 58 | extern int curcol; /* Cursor column */ 59 | extern int thisflag; /* Flags, this command */ 60 | extern int lastflag; /* Flags, last command */ 61 | extern int curgoal; /* Goal for C-P, C-N */ 62 | extern WINDOW *curwp; /* Current window */ 63 | extern BUFFER *curbp; /* Current buffer */ 64 | extern WINDOW *wheadp; /* Head of list of windows */ 65 | extern BUFFER *bheadp; /* Head of list of buffers */ 66 | extern BUFFER *blistp; /* Buffer for C-X C-B */ 67 | extern short *kbdmip; /* Input pointer for above */ 68 | extern short *kbdmop; /* Output pointer for above */ 69 | 70 | #endif 71 | 72 | /* terminal table defined only in TERM.C */ 73 | 74 | #ifndef termdef 75 | extern TERM term; /* Terminal information */ 76 | #endif 77 | -------------------------------------------------------------------------------- /fileio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The routines in this file read and write ASCII files from the disk. All of 3 | * the knowledge about files is here. A better message writing scheme should 4 | * be used 5 | */ 6 | 7 | #include /* fopen(3), et.al. */ 8 | #include "estruct.h" 9 | 10 | extern void mlwrite (); 11 | 12 | int ffropen (char *fn); 13 | int ffwopen (char *fn); 14 | int ffclose (); 15 | int ffputline (char buf[], int nbuf); 16 | int ffgetline (char buf[], int nbuf); 17 | 18 | FILE *ffp; /* File pointer, all functions */ 19 | 20 | /* 21 | * Open a file for reading. 22 | */ 23 | int ffropen (char *fn) 24 | { 25 | if ((ffp = fopen (fn, "r")) == NULL) 26 | return (FIOFNF); 27 | return (FIOSUC); 28 | } 29 | 30 | /* 31 | * Open a file for writing. Return TRUE if all is well, and FALSE on error 32 | * (cannot create). 33 | */ 34 | int ffwopen (char *fn) 35 | { 36 | if ((ffp = fopen (fn, "w")) == NULL) 37 | { 38 | mlwrite ("Cannot open file for writing"); 39 | return (FIOERR); 40 | } 41 | return (FIOSUC); 42 | } 43 | 44 | /* 45 | * Close a file. Should look at the status in all systems. 46 | */ 47 | int ffclose () 48 | { 49 | if (fclose (ffp) != FALSE) 50 | { 51 | mlwrite ("Error closing file"); 52 | return (FIOERR); 53 | } 54 | return (FIOSUC); 55 | } 56 | 57 | /* 58 | * Write a line to the already opened file. The "buf" points to the buffer, 59 | * and the "nbuf" is its length, less the free newline. Return the status. 60 | * Check only at the newline. 61 | */ 62 | int ffputline (char buf[], int nbuf) 63 | { 64 | int i; 65 | 66 | for (i = 0; i < nbuf; ++i) 67 | fputc (buf[i] & 0xFF, ffp); 68 | 69 | fputc ('\n', ffp); 70 | 71 | if (ferror (ffp)) 72 | { 73 | mlwrite ("Write I/O error"); 74 | return (FIOERR); 75 | } 76 | return (FIOSUC); 77 | } 78 | 79 | /* 80 | * Read a line from a file, and store the bytes in the supplied buffer. The 81 | * "nbuf" is the length of the buffer. Complain about long lines and lines at 82 | * the end of the file that don't have a newline present. Check for I/O errors 83 | * too. Return status. 84 | */ 85 | int ffgetline (char buf[], int nbuf) 86 | { 87 | int c, i; 88 | 89 | i = 0; 90 | 91 | while ((c = fgetc (ffp)) != EOF && c != '\n') 92 | { 93 | if (i >= nbuf - 2) 94 | { 95 | buf[nbuf - 2] = c; /* store last char read */ 96 | buf[nbuf - 1] = 0; /* and terminate it */ 97 | mlwrite ("File has long line"); 98 | return (FIOLNG); 99 | } 100 | buf[i++] = c; 101 | } 102 | 103 | if (c == EOF) 104 | { 105 | if (ferror (ffp)) 106 | { 107 | mlwrite ("File read error"); 108 | return (FIOERR); 109 | } 110 | if (i != 0) 111 | { 112 | mlwrite ("File has funny line at EOF"); 113 | return (FIOERR); 114 | } 115 | return (FIOEOF); 116 | } 117 | buf[i] = 0; 118 | return (FIOSUC); 119 | } 120 | -------------------------------------------------------------------------------- /tcap.c: -------------------------------------------------------------------------------- 1 | /* termios video driver */ 2 | 3 | #define termdef 1 /* don't define "term" external */ 4 | 5 | #include /* puts(3), snprintf(3) */ 6 | #include "estruct.h" 7 | #include "edef.h" 8 | #undef CTRL 9 | #include 10 | 11 | extern int tgetent(); 12 | extern char *tgetstr(); 13 | extern char *tgoto(); 14 | extern void tputs(); 15 | 16 | extern char *getenv(); 17 | extern void ttopen(); 18 | extern int ttgetc(); 19 | extern void ttputc(); 20 | extern void ttflush(); 21 | extern void ttclose(); 22 | 23 | void getwinsize(); 24 | void tcapopen(); 25 | void tcapmove(int row, int col); 26 | void tcapeeol(); 27 | void tcapeeop(); 28 | void tcaprev(); 29 | void tcapbeep(); 30 | 31 | #define MARGIN 8 32 | #define SCRSIZ 64 33 | #define BEL 0x07 34 | #define TCAPSLEN 64 35 | 36 | char tcapbuf[TCAPSLEN]; /* capabilities actually used */ 37 | char *CM, *CE, *CL, *SO, *SE; 38 | 39 | TERM term = { 40 | 0, 0, MARGIN, SCRSIZ, tcapopen, ttclose, ttgetc, ttputc, 41 | ttflush, tcapmove, tcapeeol, tcapeeop, tcapbeep, tcaprev 42 | }; 43 | 44 | void getwinsize () 45 | { 46 | int cols, rows; 47 | #ifndef FORCE_COLS 48 | struct winsize ws; 49 | 50 | ioctl (0, TIOCGWINSZ, &ws); 51 | cols = ws.ws_col; 52 | rows = ws.ws_row; 53 | #else 54 | cols = FORCE_COLS; 55 | rows = FORCE_ROWS; 56 | #endif 57 | if ((cols < 10) || (rows < 3)) 58 | { 59 | puts ("Unbelievable screen size\n"); 60 | exit (1); 61 | } 62 | term.t_ncol = cols; 63 | term.t_nrow = rows-1; 64 | } 65 | 66 | void tcapopen () 67 | { 68 | char tcbuf[1024], err_str[72]; 69 | char *p, *tv_stype; 70 | 71 | if ((tv_stype = getenv ("TERM")) == NULL) 72 | { 73 | puts ("Environment variable TERM not defined\n"); 74 | exit (1); 75 | } 76 | if ((tgetent (tcbuf, tv_stype)) != 1) 77 | { 78 | snprintf (err_str, 72, "Unknown terminal type %s\n", tv_stype); 79 | puts (err_str); 80 | exit (1); 81 | } 82 | p = tcapbuf; 83 | CL = tgetstr ("cl", &p); 84 | CM = tgetstr ("cm", &p); 85 | CE = tgetstr ("ce", &p); 86 | SE = tgetstr ("se", &p); 87 | SO = tgetstr ("so", &p); 88 | 89 | if (CE == NULL) 90 | eolexist = FALSE; 91 | if (SO != NULL && SE != NULL) 92 | revexist = TRUE; 93 | if (CL == NULL || CM == NULL) 94 | { 95 | puts ("Insufficient termcap! (needs cl & cm abilities)\n"); 96 | exit (1); 97 | } 98 | if (p >= &tcapbuf[TCAPSLEN]) /* XXX */ 99 | { 100 | puts ("Terminal description too big!\n"); 101 | exit (1); 102 | } 103 | ttopen (); 104 | } 105 | 106 | void tcaprev (int state) 107 | { 108 | if (revexist) 109 | tputs ((state ? SO : SE), 1, ttputc); 110 | } 111 | 112 | void tcapmove (int row, int col) 113 | { 114 | tputs (tgoto (CM, col, row), 1, ttputc); 115 | } 116 | 117 | void tcapeeol () 118 | { 119 | tputs (CE, 1, ttputc); 120 | } 121 | 122 | void tcapeeop () 123 | { 124 | tputs (CL, 1, ttputc); 125 | } 126 | 127 | void tcapbeep () 128 | { 129 | ttputc (BEL); 130 | } 131 | -------------------------------------------------------------------------------- /ee.1: -------------------------------------------------------------------------------- 1 | .\" All source code and documentation to 'Ersatz Emacs' is in the Public 2 | .\" Domain, as were all the original sources I used. I might be a rabid 3 | .\" Stallmanite weenie, but it would be improper to publish this under a 4 | .\" difference licence than it was given to me with. 5 | .\" -- Chris Baird,, 6 | .\" 7 | .Dd September 11, 2000 8 | .Os Miscellaneous\ Software 9 | .Dt EE 1 10 | .Sh NAME 11 | .Nm ee 12 | .Nd Ersatz Emacs, the simple text editor 13 | .Sh SYNOPSIS 14 | .Nm ee 15 | .Op Ar 16 | .Sh DESCRIPTION 17 | Ersatz Emacs is a very minimal imitation of the famous 18 | .Tn GNU Emacs 19 | editor. Unlike most popular Emacs derivatives, Ersatz strives to use as little 20 | system resources as possible and be simple enough for the casual programmer to 21 | understand, yet still include all the functionality required for most text 22 | editing jobs. You are encouraged to study the software sources and add any 23 | missing 24 | .Qq essential 25 | features to produce your own perfect editor. 26 | .Pp 27 | If invoked with no file arguments, the editor creates a 28 | .Qq main 29 | buffer that should be renamed 30 | .Ic ( C-x n ) 31 | and written to disk 32 | .Ic ( C-x C-s ) 33 | if the user intends to preserve the contents. 34 | .Sh ENVIRONMENT 35 | .Ev Em TERM 36 | informs the editor to capabilities of the user's terminal. Also the values of 37 | .Ev Em TERMCAP , TERMPATH , 38 | and 39 | .Ev Em HOME 40 | must be sane for 41 | .Xr termcap 5 42 | to function properly. 43 | .Sh FILES 44 | None, aside from those implied by 45 | .Xr termcap 5 . 46 | .Pp 47 | Your system manager should have copied the supplied printer-ready chart of key 48 | bindings to 49 | .Pa /usr/local/share/ERSATZ.keys . 50 | Carry this with you at all times until everything is permanent etched in 51 | wetware. One could also learn much from the tutorial in-built to 52 | .Tn GNU Emacs , 53 | as Ersatz supports many 54 | .Qq standard 55 | bindings. 56 | .Sh DIAGNOSTICS 57 | .Bl -ohang 58 | .It Em Unbelievable screen size 59 | The terminal options (see 60 | .Xr termios 4 ) 61 | have undefined column or row size information, and is usually a sign of a 62 | broken configuration somewhere. A temporary fix on UN*X is to type at the 63 | shell: 64 | .Ic stty cols 80 rows 24 65 | .It Em Environment variable TERM not defined 66 | As it says. The majority of users would have TERM = vt100 or 67 | thereabouts. 68 | .It Em Insufficient termcap (needs cl & cm abilities) 69 | Ersatz requires a terminal that can clear the screen and position the cursor. A 70 | serious system problem if not logged-in via a line-printer. 71 | .It Em Terminal description too big 72 | .It Em Can't read terminal capabilities 73 | .It Em Can't set terminal mode 74 | .It Em Can't restore terminal flags 75 | All the above are likely to involve a serious system problem. 76 | .El 77 | .Sh AUTHORS 78 | Ersatz is a derivative work of MicroEMACS 3.6, as released to mod.sources and 79 | the Public Domain by Daniel Lawrence in 1986, and is itself based on the work 80 | of Steve Wilhite and George Jones to MicroEMACS 2.0 (then also public domain) 81 | by Dave Conroy. 82 | .Pp 83 | Chris Baird stripped out most of the features he 84 | personally never used from 3.6, played with it for twelve years, completed the 85 | key-bindings chart, wrote a man-page, and produced what you see today. His 86 | additions are in the Public Domain as well. 87 | .Sh BUGS 88 | None known. There are however a few discrepancies in imitating 89 | .Tn GNU Emacs . 90 | .Pp 91 | If you should find a juicy bug, please email a good description to Chris. He 92 | however has little interest incorporating new features unless they repair 93 | serious problems or simplify the program further. 94 | .Sh SECURITY CONSIDERATIONS 95 | As file writes are done 96 | .Qq over the top of 97 | the existing file, the file ownership and permission bits are preserved, 98 | however if an error should occur during a save this may leave a damaged file on 99 | the filesystem. 100 | .Pp 101 | However, be assured that Ersatz 102 | .Em does not 103 | do stupid things like automatic word-wrap. 104 | -------------------------------------------------------------------------------- /region.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The routines in this file deal with the region, that magic space between 3 | * "." and mark. Some functions are commands. Some functions are just for 4 | * internal use 5 | */ 6 | 7 | #include "estruct.h" 8 | #include "edef.h" 9 | 10 | extern void kdelete (); 11 | extern int ldelete (int f, int n); 12 | extern int kinsert (int c); 13 | extern void mlwrite (); 14 | 15 | int killregion (int f, int n); 16 | int copyregion (int f, int n); 17 | int getregion (REGION *rp); 18 | 19 | /* 20 | * Kill the region. Ask "getregion" to figure out the bounds of the region. 21 | * Move "." to the start, and kill the characters. Bound to "C-W" 22 | */ 23 | int killregion (int f, int n) 24 | { 25 | REGION region; 26 | int s; 27 | 28 | if ((s = getregion (®ion)) != TRUE) 29 | return (s); 30 | if ((lastflag & CFKILL) == 0) /* This is a kill type */ 31 | kdelete (); /* command, so do magic */ 32 | thisflag |= CFKILL; /* kill buffer stuff */ 33 | curwp->w_dotp = region.r_linep; 34 | curwp->w_doto = region.r_offset; 35 | return (ldelete (region.r_size, TRUE)); 36 | } 37 | 38 | /* 39 | * Copy all of the characters in the region to the kill buffer. Don't move dot 40 | * at all. This is a bit like a kill region followed by a yank. Bound to "M-W" 41 | */ 42 | int copyregion (int f, int n) 43 | { 44 | LINE *linep; 45 | REGION region; 46 | int loffs, s; 47 | 48 | if ((s = getregion (®ion)) != TRUE) 49 | return (s); 50 | if ((lastflag & CFKILL) == 0) /* Kill type command */ 51 | kdelete (); 52 | thisflag |= CFKILL; 53 | linep = region.r_linep; /* Current line */ 54 | loffs = region.r_offset; /* Current offset */ 55 | while (region.r_size--) 56 | { 57 | if (loffs == llength (linep)) 58 | { /* End of line */ 59 | if ((s = kinsert ('\n')) != TRUE) 60 | return (s); 61 | linep = lforw (linep); 62 | loffs = 0; 63 | } 64 | else 65 | { /* Middle of line */ 66 | if ((s = kinsert (lgetc (linep, loffs))) != TRUE) 67 | return (s); 68 | ++loffs; 69 | } 70 | } 71 | return (TRUE); 72 | } 73 | 74 | /* 75 | * This routine figures out the bounds of the region in the current window, 76 | * and fills in the fields of the "REGION" structure pointed to by "rp". 77 | * Because the dot and mark are usually very close together, we scan outward 78 | * from dot looking for mark. This should save time. Return a standard code. 79 | * Callers of this routine should be prepared to get an "ABORT" status; we 80 | * might make this have the conform thing later 81 | */ 82 | int getregion (REGION *rp) 83 | { 84 | LINE *flp, *blp; 85 | int fsize, bsize; 86 | 87 | if (curwp->w_markp == (struct LINE*)0) 88 | { 89 | mlwrite ("No mark set in this window"); 90 | return (FALSE); 91 | } 92 | if (curwp->w_dotp == curwp->w_markp) 93 | { 94 | rp->r_linep = curwp->w_dotp; 95 | if (curwp->w_doto < curwp->w_marko) 96 | { 97 | rp->r_offset = curwp->w_doto; 98 | rp->r_size = curwp->w_marko - curwp->w_doto; 99 | } 100 | else 101 | { 102 | rp->r_offset = curwp->w_marko; 103 | rp->r_size = curwp->w_doto - curwp->w_marko; 104 | } 105 | return (TRUE); 106 | } 107 | blp = curwp->w_dotp; 108 | bsize = curwp->w_doto; 109 | flp = curwp->w_dotp; 110 | fsize = llength (flp) - curwp->w_doto + 1; 111 | while (flp != curbp->b_linep || lback (blp) != curbp->b_linep) 112 | { 113 | if (flp != curbp->b_linep) 114 | { 115 | flp = lforw (flp); 116 | if (flp == curwp->w_markp) 117 | { 118 | rp->r_linep = curwp->w_dotp; 119 | rp->r_offset = curwp->w_doto; 120 | rp->r_size = fsize + curwp->w_marko; 121 | return (TRUE); 122 | } 123 | fsize += llength (flp) + 1; 124 | } 125 | if (lback (blp) != curbp->b_linep) 126 | { 127 | blp = lback (blp); 128 | bsize += llength (blp) + 1; 129 | if (blp == curwp->w_markp) 130 | { 131 | rp->r_linep = blp; 132 | rp->r_offset = curwp->w_marko; 133 | rp->r_size = bsize - curwp->w_marko; 134 | return (TRUE); 135 | } 136 | } 137 | } 138 | mlwrite ("Bug: lost mark"); 139 | return (FALSE); 140 | } 141 | -------------------------------------------------------------------------------- /efunc.h: -------------------------------------------------------------------------------- 1 | /* EFUNC.H: function declarations and names 2 | * 3 | * This file list all the C code functions used. To add functions, declare it 4 | * here in both the extern function list and the name binding table 5 | */ 6 | 7 | extern int ctrlg (); /* Abort out of things */ 8 | extern int quit (); /* Quit */ 9 | extern int ctlxlp (); /* Begin macro */ 10 | extern int ctlxrp (); /* End macro */ 11 | extern int ctlxe (); /* Execute macro */ 12 | extern int fileread (); /* Get a file, read only */ 13 | extern int filefind (); /* Get a file, read write */ 14 | extern int filewrite (); /* Write a file */ 15 | extern int filesave (); /* Save current file */ 16 | extern int filename (); /* Adjust file name */ 17 | extern int getccol (); /* Get current column */ 18 | extern int gotobol (); /* Move to start of line */ 19 | extern int forwchar (); /* Move forward by characters */ 20 | extern int gotoeol (); /* Move to end of line */ 21 | extern int backchar (); /* Move backward by characters */ 22 | extern int forwline (); /* Move forward by lines */ 23 | extern int backline (); /* Move backward by lines */ 24 | extern int forwpage (); /* Move forward by pages */ 25 | extern int backpage (); /* Move backward by pages */ 26 | extern int gotobob (); /* Move to start of buffer */ 27 | extern int gotoeob (); /* Move to end of buffer */ 28 | extern int setfillcol (); /* Set fill column */ 29 | extern int setmark (); /* Set mark */ 30 | extern int forwsearch (); /* Search forward */ 31 | extern int backsearch (); /* Search backwards */ 32 | extern int sreplace (); /* search and replace */ 33 | extern int qreplace (); /* search and replace w/query */ 34 | extern int showcpos (); /* Show the cursor position */ 35 | extern int nextwind (); /* Move to the next window */ 36 | extern int prevwind (); /* Move to the previous window */ 37 | extern int onlywind (); /* Make current window only one */ 38 | extern int splitwind (); /* Split current window */ 39 | extern int enlargewind (); /* Enlarge display window */ 40 | extern int shrinkwind (); /* Shrink window */ 41 | extern int listbuffers (); /* Display list of buffers */ 42 | extern int usebuffer (); /* Switch a window to a buffer */ 43 | extern int killbuffer (); /* Make a buffer go away */ 44 | extern int refresh (); /* Refresh the screen */ 45 | extern int twiddle (); /* Twiddle characters */ 46 | extern int tab (); /* Insert tab */ 47 | extern int newline (); /* Insert CR-LF */ 48 | extern int openline (); /* Open up a blank line */ 49 | extern int quote (); /* Insert literal */ 50 | extern int backword (); /* Backup by words */ 51 | extern int forwword (); /* Advance by words */ 52 | extern int forwdel (); /* Forward delete */ 53 | extern int backdel (); /* Backward delete */ 54 | extern int killtext (); /* Kill forward */ 55 | extern int yank (); /* Yank back from killbuffer */ 56 | extern int upperword (); /* Upper case word */ 57 | extern int lowerword (); /* Lower case word */ 58 | extern int capword (); /* Initial capitalize word */ 59 | extern int delfword (); /* Delete forward word */ 60 | extern int delbword (); /* Delete backward word */ 61 | extern int killregion (); /* Kill region */ 62 | extern int copyregion (); /* Copy region to kill buffer */ 63 | extern int quickexit (); /* low keystroke style exit */ 64 | extern int gotoline (); /* go to a numbered line */ 65 | extern int namebuffer (); /* rename the current buffer */ 66 | extern int gotobop (); /* go to begining/paragraph */ 67 | extern int gotoeop (); /* go to end/paragraph */ 68 | extern int fillpara (); /* fill current paragraph */ 69 | extern int deskey (); /* describe a key's binding */ 70 | extern int insfile (); /* insert a file */ 71 | extern int scrnextup (); /* scroll next window back */ 72 | extern int scrnextdw (); /* scroll next window down */ 73 | extern int nextbuffer (); /* switch to the next buffer */ 74 | extern int forwhunt (); /* hunt forward for next match */ 75 | extern int backhunt (); /* hunt backwards for next match */ 76 | extern int extendedcmd (); /* parse ANSI/VT100 extended keys */ 77 | -------------------------------------------------------------------------------- /ERSATZ.keys: -------------------------------------------------------------------------------- 1 | Ersatz Emacs (2000/09/14) 2 | 3 | M- means to use the key prior to using another key 4 | ^A means to use the control key at the same time as the 'A' key 5 | 6 | ------------------------------------------------------------------------------ 7 | MOVING THE CURSOR 8 | 9 | ^F Forward character M-F Forward word 10 | ^B Backward character M-B Backward word 11 | ^N Next line M-P Front of paragraph 12 | ^P Previous line M-N End of paragraph 13 | ^A Front of line M-< or [HOME] Start of file 14 | ^E End of line M-> or [END] End of file 15 | ^V or [Page Down] Scroll down 16 | M-V or [Page Up] Scroll up Arrow keys are active 17 | 18 | ------------------------------------------------------------------------------ 19 | DELETING & INSERTING 20 | 21 | <- Delete previous character M-<- Delete previous word 22 | ^D Delete next character M-D Delete next word 23 | ^K Delete to end of line ^O Insert line 24 | 25 | ------------------------------------------------------------------------------ 26 | FORMATTING & TRANSPOSING 27 | 28 | M-U UPPERCASE word M-C Capitalize word 29 | M-L lowercase word ^T Transpose characters 30 | ^Q Quote next key, so that control codes may be entered into text. 31 | M-Q Format paragraph so that text is left-justified between margins. 32 | ^X F Set the right margin for paragraph formatting to the current position of 33 | the cursor. 34 | 35 | ------------------------------------------------------------------------------ 36 | SEARCHING 37 | 38 | ^S Search forward from cursor position. Type in a string and end it with 39 | ESC. Either case matches. 40 | ^R As above, but reverse search from cursor position. 41 | 42 | ------------------------------------------------------------------------------ 43 | REPLACING 44 | 45 | M-R Replace all instances of first typed-in string with second typed-in 46 | string. End each string with ESC. 47 | M-% Replace with query. Answer with: 48 | Y replace & continue N no replacement & continue 49 | ! replace the rest ? Get a list of options 50 | . exit and return to entry point 51 | ^G,'q' or exit and remain at current location 52 | 53 | ------------------------------------------------------------------------------ 54 | COPYING AND MOVING 55 | 56 | ^@ or M- Set mark at current position. 57 | ^W Delete region. 58 | M-W Copy region to kill buffer. 59 | ^Y Yank back kill buffer at cursor. 60 | 61 | A region is defined as the area between this mark and the current cursor 62 | position. The kill buffer is the text which has been most recently deleted or 63 | copied. 64 | 65 | Generally, the procedure for copying or moving text is: 66 | 1) Mark out region using M- at the beginning and move the cursor to 67 | the end. 68 | 2) Delete it (with ^W) or copy it (with M-W) into the kill buffer. 69 | 3) Move the cursor to the desired location and yank it back (with ^Y). 70 | 71 | ------------------------------------------------------------------------------ 72 | MULTIPLE BUFFERS 73 | 74 | A buffer contains a COPY of a document being edited, and must be saved for 75 | changes to be kept. Many buffers may be activated at once. 76 | 77 | ^X B Switch to another buffer. 78 | ^X ^B Show buffer directory in a window (^X 1 to remove). 79 | ^X K Delete a non-displayed buffer. 80 | ^X X Switch to next buffer in buffer list. 81 | ^X N Change the filename associated with the buffer. 82 | M-N Change the name of the buffer. 83 | 84 | ------------------------------------------------------------------------------ 85 | READING FROM DISK 86 | 87 | ^X^F Find file; read into a new buffer created from filename. 88 | (This is the usual way to edit a new file.) 89 | ^X^R Read file into current buffer, erasing its previous contents. 90 | No new buffer will be created. 91 | ^X^I Insert file into current buffer at cursor's location. 92 | 93 | ------------------------------------------------------------------------------ 94 | SAVING TO DISK 95 | 96 | ^X^S Save current buffer to disk, using the buffer's filename as the name of 97 | the disk file. Any disk file of that name will be overwritten. 98 | ^X^W Write current buffer to disk. Type in a new filename at the prompt to 99 | write to; it will also become the current buffer's filename. 100 | 101 | ------------------------------------------------------------------------------ 102 | MULTIPLE WINDOWS 103 | 104 | Many windows may be visible at once on the screen. Windows may show different 105 | parts of the same buffer, or each may display a different one. 106 | 107 | ^X 2 Split the current window in two ^X 1 Show only current window 108 | ^X O Move cursor to next window ^X ^ Enlarge current window 109 | M-^V Scroll other window down M-^Z Scroll other window up 110 | 111 | ------------------------------------------------------------------------------ 112 | EXITING 113 | 114 | ^X^C Exit. Any unsaved files will require confirmation. 115 | M-Z Write out all changed buffers automatically and exit. 116 | 117 | ------------------------------------------------------------------------------ 118 | MACROS 119 | 120 | ^X ( Start recording a keyboard macro. Typing ^G or an error aborts. 121 | ^X ) Stop recording macro. 122 | ^X E Execute macro. 123 | 124 | ------------------------------------------------------------------------------ 125 | REPEAT & NUMBER PREFIX 126 | 127 | ^U or M- 128 | Number prefix and universal repeat. May be followed by an integer 129 | (default = 4) and repeats the next command that many times. 130 | Exceptions follow. 131 | ^U^L 132 | Reposition the cursor to a particular screen row; i.e., ^U0^L moves the 133 | cursor and the line it is on to the top of the screen. Negative numbers 134 | are from the bottom of the screen. 135 | ^UM-G 136 | Go to line . Note this is different behavour to GNU Emacs. 137 | ^U^X F 138 | Set the right margin to column for paragraph formatting. 139 | ^U^X^ 140 | Enlarge a split window by rows. A negative number shrinks the 141 | window. 142 | 143 | ------------------------------------------------------------------------------ 144 | SPECIAL KEYS 145 | 146 | ^G Cancel current command. 147 | ^L Redraws the screen completely. 148 | ^X = Position report; displays line number, buffer size, etc. 149 | 150 | ------------------------------------------------------------------------------ 151 | -------------------------------------------------------------------------------- /estruct.h: -------------------------------------------------------------------------------- 1 | /* ESTRUCT: Structure and preprocesser */ 2 | 3 | /* internal constants */ 4 | #define NFILEN 80 /* maximum # of bytes, file name */ 5 | #define NBUFN 16 /* maximum # of bytes, buffer name */ 6 | #define NLINE 512 /* maximum # of bytes, line */ 7 | #define NKBDM 256 /* maximum # of strokes, keyboard macro */ 8 | #define NPAT 80 /* maximum # of bytes, pattern */ 9 | #define HUGE 32700 /* Huge number for "impossible" row&col */ 10 | 11 | #define METACH 0x1B /* M- prefix, Control-[, ESC */ 12 | #define BELL 0x07 /* a bell character */ 13 | #define TAB 0x09 /* a tab character */ 14 | 15 | #define CTRL 0x0100 /* Control flag, or'ed in */ 16 | #define META 0x0200 /* Meta flag, or'ed in */ 17 | #define CTLX 0x0400 /* ^X flag, or'ed in */ 18 | 19 | #define FALSE 0 /* False, no, bad, etc */ 20 | #define TRUE 1 /* True, yes, good, etc */ 21 | #define ABORT 2 /* Death, ^G, abort, etc */ 22 | 23 | #define FIOSUC 0 /* File I/O, success */ 24 | #define FIOFNF 1 /* File I/O, file not found */ 25 | #define FIOEOF 2 /* File I/O, end of file */ 26 | #define FIOERR 3 /* File I/O, error */ 27 | #define FIOLNG 4 /* line longer than allowed len */ 28 | 29 | #define CFCPCN 0x0001 /* Last command was C-P, C-N */ 30 | #define CFKILL 0x0002 /* Last command was a kill */ 31 | 32 | /* 33 | * There is a window structure allocated for every active display window. The 34 | * windows are kept in a big list, in top to bottom screen order, with the 35 | * listhead at "wheadp". Each window contains its own values of dot and mark. 36 | * The flag field contains some bits that are set by commands to guide 37 | * redisplay; although this is a bit of a compromise in terms of decoupling, 38 | * the full blown redisplay is just too expensive to run for every input 39 | * character 40 | */ 41 | typedef struct WINDOW 42 | { 43 | struct WINDOW *w_wndp; /* Next window */ 44 | struct BUFFER *w_bufp; /* Buffer displayed in window */ 45 | struct LINE *w_linep; /* Top line in the window */ 46 | struct LINE *w_dotp; /* Line containing "." */ 47 | long w_doto; /* Byte offset for "." */ 48 | struct LINE *w_markp; /* Line containing "mark" */ 49 | long w_marko; /* Byte offset for "mark" */ 50 | char w_toprow; /* Origin 0 top row of window */ 51 | char w_ntrows; /* # of rows of text in window */ 52 | char w_force; /* If NZ, forcing row */ 53 | char w_flag; /* Flags */ 54 | } WINDOW; 55 | 56 | #define WFFORCE 0x01 /* Window needs forced reframe */ 57 | #define WFMOVE 0x02 /* Movement from line to line */ 58 | #define WFEDIT 0x04 /* Editing within a line */ 59 | #define WFHARD 0x08 /* Better to a full display */ 60 | #define WFMODE 0x10 /* Update mode line */ 61 | 62 | /* 63 | * Text is kept in buffers. A buffer header, described below, exists for every 64 | * buffer in the system. The buffers are kept in a big list, so that commands 65 | * that search for a buffer by name can find the buffer header. There is a 66 | * safe store for the dot and mark in the header, but this is only valid if 67 | * the buffer is not being displayed (that is, if "b_nwnd" is 0). The text for 68 | * the buffer is kept in a circularly linked list of lines, with a pointer to 69 | * the header line in "b_linep". Buffers may be "Inactive" which means the 70 | * files accosiated with them have not been read in yet. These get read in at 71 | * "use buffer" time 72 | */ 73 | typedef struct BUFFER 74 | { 75 | struct BUFFER *b_bufp; /* Link to next BUFFER */ 76 | struct LINE *b_dotp; /* Link to "." LINE structure */ 77 | long b_doto; /* Offset of "." in above LINE */ 78 | struct LINE *b_markp; /* The same as the above two, */ 79 | long b_marko; /* but for the "mark" */ 80 | struct LINE *b_linep; /* Link to the header LINE */ 81 | char b_active; /* window activated flag */ 82 | char b_nwnd; /* Count of windows on buffer */ 83 | char b_flag; /* Flags */ 84 | char b_fname[NFILEN]; /* File name */ 85 | char b_bname[NBUFN]; /* Buffer name */ 86 | } BUFFER; 87 | 88 | #define BFTEMP 0x01 /* Internal temporary buffer */ 89 | #define BFCHG 0x02 /* Changed since last write */ 90 | 91 | /* 92 | * The starting position of a region, and the size of the region in 93 | * characters, is kept in a region structure. Used by the region commands 94 | */ 95 | typedef struct 96 | { 97 | struct LINE *r_linep; /* Origin LINE address */ 98 | long r_offset; /* Origin LINE offset */ 99 | long r_size; /* Length in characters */ 100 | } REGION; 101 | 102 | /* 103 | * All text is kept in circularly linked lists of "LINE" structures. These 104 | * begin at the header line (which is the blank line beyond the end of the 105 | * buffer). This line is pointed to by the "BUFFER". Each line contains a the 106 | * number of bytes in the line (the "used" size), the size of the text array, 107 | * and the text. The end of line is not stored as a byte; it's implied. Future 108 | * additions will include update hints, and a list of marks into the line 109 | */ 110 | typedef struct LINE 111 | { 112 | struct LINE *l_fp; /* Link to the next line */ 113 | struct LINE *l_bp; /* Link to the previous line */ 114 | int l_size; /* Allocated size */ 115 | int l_used; /* Used size */ 116 | char l_text[1]; /* A bunch of characters */ 117 | } LINE; 118 | 119 | #define lforw(lp) ((lp)->l_fp) 120 | #define lback(lp) ((lp)->l_bp) 121 | #define lgetc(lp, n) ((lp)->l_text[(n)]&0xFF) 122 | #define lputc(lp, n, c) ((lp)->l_text[(n)]=(c)) 123 | #define llength(lp) ((lp)->l_used) 124 | 125 | /* 126 | * The editor communicates with the display using a high level interface. A 127 | * "TERM" structure holds useful variables, and indirect pointers to routines 128 | * that do useful operations. The low level get and put routines are here too. 129 | * This lets a terminal, in addition to having non standard commands, have 130 | * funny get and put character code too. The calls might get changed to 131 | * "termp->t_field" style in the future, to make it possible to run more than 132 | * one terminal type 133 | */ 134 | typedef struct 135 | { 136 | int t_nrow; /* Number of rows */ 137 | int t_ncol; /* Number of columns */ 138 | int t_margin; /* min margin for extended lines */ 139 | int t_scrsiz; /* size of scroll region " */ 140 | void (*t_open) (); /* Open terminal at the start */ 141 | void (*t_close) (); /* Close terminal at end */ 142 | int (*t_getchar) (); /* Get character from keyboard */ 143 | void (*t_putchar) (); /* Put character to display */ 144 | void (*t_flush) (); /* Flush output buffers */ 145 | void (*t_move) (); /* Move the cursor, origin 0 */ 146 | void (*t_eeol) (); /* Erase to end of line */ 147 | void (*t_eeop) (); /* Erase to end of page */ 148 | void (*t_beep) (); /* Beep */ 149 | void (*t_rev) (); /* set reverse video state */ 150 | } TERM; 151 | 152 | /* structure for the table of initial key bindings */ 153 | 154 | typedef struct 155 | { 156 | short k_code; /* Key code */ 157 | int (*k_fp) (); /* Routine to handle it */ 158 | } KEYTAB; 159 | -------------------------------------------------------------------------------- /random.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file contains the command processing functions for a number of random 3 | * commands. There is no functional grouping here, for sure. 4 | */ 5 | 6 | #include "estruct.h" 7 | #include "edef.h" 8 | 9 | extern void mlwrite (); 10 | extern void lchange (int flag); 11 | extern int lnewline (); 12 | extern int linsert (int n, int c); 13 | extern int backchar (int f, int n); 14 | extern void kdelete (); 15 | extern int ldelete (int f, int n); 16 | extern int kremove (int k); 17 | 18 | int setfillcol (int f, int n); 19 | int showcpos (int f, int n); 20 | int getccol (int bflg); 21 | int twiddle (int f, int n); 22 | int quote (int f, int n); 23 | int tab (int f, int n); 24 | int openline (int f, int n); 25 | int newline (int f, int n); 26 | int forwdel (int f, int n); 27 | int backdel (int f, int n); 28 | int killtext (int f, int n); 29 | int yank (int f, int n); 30 | 31 | /* 32 | * Set fill column to n. 33 | */ 34 | int setfillcol (int f, int n) 35 | { 36 | fillcol = n; 37 | mlwrite ("[Fill column is %d]", n); 38 | return (TRUE); 39 | } 40 | 41 | /* 42 | * Display the current position of the cursor, in origin 1 X-Y coordinates, 43 | * the character that is under the cursor (in hex), and the fraction of the 44 | * text that is before the cursor. The displayed column is not the current 45 | * column, but the column that would be used on an infinite width display. 46 | * Normally this is bound to "C-X =" 47 | */ 48 | int showcpos (int f, int n) 49 | { 50 | LINE *clp; 51 | long nch, nbc; 52 | int cbo, cac, ratio, col; 53 | 54 | clp = lforw (curbp->b_linep); /* Grovel the data */ 55 | cbo = 0; 56 | nch = 0; 57 | nbc = 0; 58 | cac = 0; 59 | for (;;) 60 | { 61 | if (clp == curwp->w_dotp && cbo == curwp->w_doto) 62 | { 63 | nbc = nch; 64 | if (cbo == llength (clp)) 65 | cac = '\n'; 66 | else 67 | cac = lgetc (clp, cbo); 68 | } 69 | if (cbo == llength (clp)) 70 | { 71 | if (clp == curbp->b_linep) 72 | break; 73 | clp = lforw (clp); 74 | cbo = 0; 75 | } 76 | else 77 | ++cbo; 78 | ++nch; 79 | } 80 | col = getccol (FALSE); /* Get real column */ 81 | ratio = 0; /* Ratio before dot */ 82 | if (nch != 0) 83 | ratio = (100L * nbc) / nch; 84 | mlwrite ("Char: %c (0%o, %d, 0x%x) point=%D of %D(%d%%) column %d", 85 | ((cac > 31) && (cac < 128) ? cac : 32), 86 | cac, cac, cac, nbc + 1, nch, ratio, col); 87 | return (TRUE); 88 | } 89 | 90 | /* 91 | * Return current column. Stop at first non-blank given TRUE argument. 92 | */ 93 | int getccol (int bflg) 94 | { 95 | int c, i, col; 96 | 97 | col = 0; 98 | for (i = 0; i < curwp->w_doto; ++i) 99 | { 100 | c = lgetc (curwp->w_dotp, i); 101 | if (c != ' ' && c != '\t' && bflg) 102 | break; 103 | if (c == '\t') 104 | col |= 0x07; 105 | else if (c < 0x20 || c == 0x7F) 106 | ++col; 107 | ++col; 108 | } 109 | return (col); 110 | } 111 | 112 | /* 113 | * Twiddle the two characters on either side of dot. If dot is at the end of 114 | * the line twiddle the two characters before it. Return with an error if dot 115 | * is at the beginning of line; it seems to be a bit pointless to make this 116 | * work. This fixes up a very common typo with a single stroke. Normally bound 117 | * to "C-T". This always works within a line, so "WFEDIT" is good enough 118 | */ 119 | int twiddle (int f, int n) 120 | { 121 | LINE *dotp; 122 | int doto, cl, cr; 123 | 124 | dotp = curwp->w_dotp; 125 | doto = curwp->w_doto; 126 | if (doto == llength (dotp) && --doto < 0) 127 | return (FALSE); 128 | cr = lgetc (dotp, doto); 129 | if (--doto < 0) 130 | return (FALSE); 131 | cl = lgetc (dotp, doto); 132 | lputc (dotp, doto + 0, cr); 133 | lputc (dotp, doto + 1, cl); 134 | lchange (WFEDIT); 135 | return (TRUE); 136 | } 137 | 138 | /* 139 | * Quote the next character, and insert it into the buffer. All the characters 140 | * are taken literally, with the exception of the newline, which always has 141 | * its line splitting meaning. The character is always read, even if it is 142 | * inserted 0 times, for regularity. Bound to "C-Q" 143 | */ 144 | int quote (int f, int n) 145 | { 146 | int s, c; 147 | 148 | c = (*term.t_getchar) (); 149 | if (n < 0) 150 | return (FALSE); 151 | if (n == 0) 152 | return (TRUE); 153 | if (c == '\n') 154 | { 155 | do 156 | { 157 | s = lnewline (); 158 | } 159 | while (s == TRUE && --n); 160 | return (s); 161 | } 162 | return (linsert (n, c)); 163 | } 164 | 165 | /* 166 | * Insert a tab into file. 167 | * Bound to "C-I" 168 | */ 169 | int tab (int f, int n) 170 | { 171 | if (n < 0) 172 | return (FALSE); 173 | return (linsert (n, 9)); 174 | } 175 | 176 | /* 177 | * Open up some blank space. The basic plan is to insert a bunch of newlines, 178 | * and then back up over them. Everything is done by the subcommand 179 | * processors. They even handle the looping. Normally this is bound to "C-O" 180 | */ 181 | int openline (int f, int n) 182 | { 183 | int i, s; 184 | 185 | if (n < 0) 186 | return (FALSE); 187 | if (n == 0) 188 | return (TRUE); 189 | i = n; /* Insert newlines */ 190 | do 191 | { 192 | s = lnewline (); 193 | } 194 | while (s == TRUE && --i); 195 | if (s == TRUE) /* Then back up overtop */ 196 | s = backchar (f, n); /* of them all */ 197 | return (s); 198 | } 199 | 200 | /* 201 | * Insert a newline. Bound to "C-M". 202 | */ 203 | int newline (int f, int n) 204 | { 205 | int s; 206 | 207 | if (n < 0) 208 | return (FALSE); 209 | 210 | /* insert some lines */ 211 | while (n--) 212 | { 213 | if ((s = lnewline ()) != TRUE) 214 | return (s); 215 | } 216 | return (TRUE); 217 | } 218 | 219 | /* 220 | * Delete forward. This is real easy, because the basic delete routine does 221 | * all of the work. Watches for negative arguments, and does the right thing. 222 | * If any argument is present, it kills rather than deletes, to prevent loss 223 | * of text if typed with a big argument. Normally bound to "C-D" 224 | */ 225 | int forwdel (int f, int n) 226 | { 227 | if (n < 0) 228 | return (backdel (f, -n)); 229 | if (f != FALSE) 230 | { /* Really a kill */ 231 | if ((lastflag & CFKILL) == 0) 232 | kdelete (); 233 | thisflag |= CFKILL; 234 | } 235 | return (ldelete (n, f)); 236 | } 237 | 238 | /* 239 | * Delete backwards. This is quite easy too, because it's all done with other 240 | * functions. Just move the cursor back, and delete forwards. Like delete 241 | * forward, this actually does a kill if presented with an argument. Bound to 242 | * both "RUBOUT" and "C-H" 243 | */ 244 | int backdel (int f, int n) 245 | { 246 | int s; 247 | 248 | if (n < 0) 249 | return (forwdel (f, -n)); 250 | if (f != FALSE) 251 | { /* Really a kill */ 252 | if ((lastflag & CFKILL) == 0) 253 | kdelete (); 254 | thisflag |= CFKILL; 255 | } 256 | if ((s = backchar (f, n)) == TRUE) 257 | s = ldelete (n, f); 258 | return (s); 259 | } 260 | 261 | /* 262 | * Kill text. If called without an argument, it kills from dot to the end of 263 | * the line, unless it is at the end of the line, when it kills the newline. 264 | * If called with an argument of 0, it kills from the start of the line to 265 | * dot. If called with a positive argument, it kills from dot forward over 266 | * that number of newlines. If called with a negative argument it kills 267 | * backwards that number of newlines. Normally bound to "C-K" 268 | */ 269 | int killtext (int f, int n) 270 | { 271 | LINE *nextp; 272 | int chunk; 273 | 274 | if ((lastflag & CFKILL) == 0)/* Clear kill buffer if last wasn't a kill */ 275 | kdelete (); 276 | thisflag |= CFKILL; 277 | if (f == FALSE) 278 | { 279 | chunk = llength (curwp->w_dotp) - curwp->w_doto; 280 | if (chunk == 0) 281 | chunk = 1; 282 | } 283 | else if (n == 0) 284 | { 285 | chunk = curwp->w_doto; 286 | curwp->w_doto = 0; 287 | } 288 | else if (n > 0) 289 | { 290 | chunk = llength (curwp->w_dotp) - curwp->w_doto + 1; 291 | nextp = lforw (curwp->w_dotp); 292 | while (--n) 293 | { 294 | if (nextp == curbp->b_linep) 295 | return (FALSE); 296 | chunk += llength (nextp) + 1; 297 | nextp = lforw (nextp); 298 | } 299 | } 300 | else 301 | { 302 | mlwrite ("neg kill"); 303 | return (FALSE); 304 | } 305 | return (ldelete (chunk, TRUE)); 306 | } 307 | 308 | /* 309 | * Yank text back from the kill buffer. This is really easy. All of the work 310 | * is done by the standard insert routines. All you do is run the loop, and 311 | * check for errors. Bound to "C-Y" 312 | */ 313 | int yank (int f, int n) 314 | { 315 | int c, i; 316 | 317 | if (n < 0) 318 | return (FALSE); 319 | while (n--) 320 | { 321 | i = 0; 322 | while ((c = kremove (i)) >= 0) 323 | { 324 | if (c == '\n') 325 | { 326 | if (lnewline (FALSE, 1) == FALSE) 327 | return (FALSE); 328 | } 329 | else 330 | { 331 | if (linsert (1, c) == FALSE) 332 | return (FALSE); 333 | } 334 | ++i; 335 | } 336 | } 337 | return (TRUE); 338 | } 339 | -------------------------------------------------------------------------------- /window.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Window management. Some of the functions are internal, and some are 3 | * attached to keys that the user actually types 4 | */ 5 | 6 | #include /* free(3), malloc(3) */ 7 | #include "estruct.h" 8 | #include "edef.h" 9 | 10 | extern void upmode (); 11 | extern void mlwrite (); 12 | extern int backpage (int f, int n); 13 | extern int forwpage (int f, int n); 14 | 15 | int refresh (int f, int n); 16 | int nextwind (int f, int n); 17 | int prevwind (int f, int n); 18 | int onlywind (int f, int n); 19 | int splitwind (int f, int n); 20 | int enlargewind (int f, int n); 21 | int shrinkwind (int f, int n); 22 | WINDOW* wpopup (); 23 | int scrnextup (int f, int n); 24 | int scrnextdw (int f, int n); 25 | 26 | /* 27 | * Refresh the screen. With no argument, it does the refresh and centers 28 | * the cursor on the screen. With an argument it does a reposition instead. 29 | * Bound to "C-L" 30 | */ 31 | int refresh (int f, int n) 32 | { 33 | if (n >= 0) 34 | n++; /* adjust to screen row */ 35 | if (f == FALSE) 36 | { 37 | sgarbf = TRUE; 38 | n = 0; /* Center dot */ 39 | } 40 | curwp->w_force = n; 41 | curwp->w_flag |= WFFORCE; 42 | return (TRUE); 43 | } 44 | 45 | /* 46 | * The command make the next window (next => down the screen) the current 47 | * window. There are no real errors, although the command does nothing if 48 | * there is only 1 window on the screen. Bound to "C-X C-N" 49 | */ 50 | int nextwind (int f, int n) 51 | { 52 | WINDOW *wp; 53 | 54 | if ((wp = curwp->w_wndp) == NULL) 55 | wp = wheadp; 56 | 57 | curwp = wp; 58 | curbp = wp->w_bufp; 59 | upmode (); 60 | return (TRUE); 61 | } 62 | 63 | /* 64 | * This command makes the previous window (previous => up the screen) the 65 | * current window. There arn't any errors, although the command does not do a 66 | * lot if there is 1 window 67 | */ 68 | int prevwind (int f, int n) 69 | { 70 | WINDOW *wp1, *wp2; 71 | 72 | wp1 = wheadp; 73 | wp2 = curwp; 74 | 75 | if (wp1 == wp2) 76 | wp2 = NULL; 77 | 78 | while (wp1->w_wndp != wp2) 79 | wp1 = wp1->w_wndp; 80 | 81 | curwp = wp1; 82 | curbp = wp1->w_bufp; 83 | upmode (); 84 | return (TRUE); 85 | } 86 | 87 | /* 88 | * This command makes the current window the only window on the screen. Bound 89 | * to "C-X 1". Try to set the framing so that "." does not have to move on the 90 | * display. Some care has to be taken to keep the values of dot and mark in 91 | * the buffer structures right if the distruction of a window makes a buffer 92 | * become undisplayed 93 | */ 94 | int onlywind (int f, int n) 95 | { 96 | WINDOW *wp; 97 | LINE *lp; 98 | int i; 99 | 100 | while (wheadp != curwp) 101 | { 102 | wp = wheadp; 103 | wheadp = wp->w_wndp; 104 | if (--wp->w_bufp->b_nwnd == 0) 105 | { 106 | wp->w_bufp->b_dotp = wp->w_dotp; 107 | wp->w_bufp->b_doto = wp->w_doto; 108 | wp->w_bufp->b_markp = wp->w_markp; 109 | wp->w_bufp->b_marko = wp->w_marko; 110 | } 111 | free ((char *) wp); 112 | } 113 | while (curwp->w_wndp != NULL) 114 | { 115 | wp = curwp->w_wndp; 116 | curwp->w_wndp = wp->w_wndp; 117 | if (--wp->w_bufp->b_nwnd == 0) 118 | { 119 | wp->w_bufp->b_dotp = wp->w_dotp; 120 | wp->w_bufp->b_doto = wp->w_doto; 121 | wp->w_bufp->b_markp = wp->w_markp; 122 | wp->w_bufp->b_marko = wp->w_marko; 123 | } 124 | free ((char *) wp); 125 | } 126 | lp = curwp->w_linep; 127 | i = curwp->w_toprow; 128 | while (i != 0 && lback (lp) != curbp->b_linep) 129 | { 130 | --i; 131 | lp = lback (lp); 132 | } 133 | curwp->w_toprow = 0; 134 | curwp->w_ntrows = term.t_nrow - 1; 135 | curwp->w_linep = lp; 136 | curwp->w_flag |= WFMODE | WFHARD; 137 | return (TRUE); 138 | } 139 | 140 | /* 141 | * Split the current window. A window smaller than 3 lines cannot be split. 142 | * The only other error that is possible is a "malloc" failure allocating the 143 | * structure for the new window. Bound to "C-X 2" 144 | */ 145 | int splitwind (int f, int n) 146 | { 147 | LINE *lp; 148 | WINDOW *wp, *wp1, *wp2; 149 | int ntru, ntrl, ntrd; 150 | 151 | if (curwp->w_ntrows < 3) 152 | { 153 | mlwrite ("Cannot split a %d line window", curwp->w_ntrows); 154 | return (FALSE); 155 | } 156 | if ((wp = (WINDOW *) malloc (sizeof (WINDOW))) == NULL) 157 | { 158 | mlwrite ("Cannot allocate WINDOW block"); 159 | return (FALSE); 160 | } 161 | ++curbp->b_nwnd; /* Displayed twice */ 162 | wp->w_bufp = curbp; 163 | wp->w_dotp = curwp->w_dotp; 164 | wp->w_doto = curwp->w_doto; 165 | wp->w_markp = curwp->w_markp; 166 | wp->w_marko = curwp->w_marko; 167 | wp->w_flag = 0; 168 | wp->w_force = 0; 169 | ntru = (curwp->w_ntrows - 1) / 2; /* Upper size */ 170 | ntrl = (curwp->w_ntrows - 1) - ntru; /* Lower size */ 171 | lp = curwp->w_linep; 172 | ntrd = 0; 173 | while (lp != curwp->w_dotp) 174 | { 175 | ++ntrd; 176 | lp = lforw (lp); 177 | } 178 | lp = curwp->w_linep; 179 | if (ntrd <= ntru) 180 | { /* Old is upper window */ 181 | if (ntrd == ntru) /* Hit mode line */ 182 | lp = lforw (lp); 183 | curwp->w_ntrows = ntru; 184 | wp->w_wndp = curwp->w_wndp; 185 | curwp->w_wndp = wp; 186 | wp->w_toprow = curwp->w_toprow + ntru + 1; 187 | wp->w_ntrows = ntrl; 188 | } 189 | else 190 | { /* Old is lower window */ 191 | wp1 = NULL; 192 | wp2 = wheadp; 193 | while (wp2 != curwp) 194 | { 195 | wp1 = wp2; 196 | wp2 = wp2->w_wndp; 197 | } 198 | if (wp1 == NULL) 199 | wheadp = wp; 200 | else 201 | wp1->w_wndp = wp; 202 | wp->w_wndp = curwp; 203 | wp->w_toprow = curwp->w_toprow; 204 | wp->w_ntrows = ntru; 205 | ++ntru; /* Mode line */ 206 | curwp->w_toprow += ntru; 207 | curwp->w_ntrows = ntrl; 208 | while (ntru--) 209 | lp = lforw (lp); 210 | } 211 | curwp->w_linep = lp; /* Adjust the top lines */ 212 | wp->w_linep = lp; /* if necessary */ 213 | curwp->w_flag |= WFMODE | WFHARD; 214 | wp->w_flag |= WFMODE | WFHARD; 215 | return (TRUE); 216 | } 217 | 218 | /* 219 | * Enlarge the current window. Find the window that loses space. Make sure it 220 | * is big enough. If so, hack the window descriptions, and ask redisplay to do 221 | * all the hard work. You don't just set "force reframe" because dot would 222 | * move. Bound to "C-X Z" 223 | */ 224 | int enlargewind (int f, int n) 225 | { 226 | WINDOW *adjwp; 227 | LINE *lp; 228 | int i; 229 | 230 | if (n < 0) 231 | return (shrinkwind (f, -n)); 232 | if (wheadp->w_wndp == NULL) 233 | { 234 | mlwrite ("Only one window"); 235 | return (FALSE); 236 | } 237 | if ((adjwp = curwp->w_wndp) == NULL) 238 | { 239 | adjwp = wheadp; 240 | while (adjwp->w_wndp != curwp) 241 | adjwp = adjwp->w_wndp; 242 | } 243 | if (adjwp->w_ntrows <= n) 244 | { 245 | mlwrite ("Impossible change"); 246 | return (FALSE); 247 | } 248 | if (curwp->w_wndp == adjwp) 249 | { /* Shrink below */ 250 | lp = adjwp->w_linep; 251 | for (i = 0; i < n && lp != adjwp->w_bufp->b_linep; ++i) 252 | lp = lforw (lp); 253 | adjwp->w_linep = lp; 254 | adjwp->w_toprow += n; 255 | } 256 | else 257 | { /* Shrink above */ 258 | lp = curwp->w_linep; 259 | for (i = 0; i < n && lback (lp) != curbp->b_linep; ++i) 260 | lp = lback (lp); 261 | curwp->w_linep = lp; 262 | curwp->w_toprow -= n; 263 | } 264 | curwp->w_ntrows += n; 265 | adjwp->w_ntrows -= n; 266 | curwp->w_flag |= WFMODE | WFHARD; 267 | adjwp->w_flag |= WFMODE | WFHARD; 268 | return (TRUE); 269 | } 270 | 271 | /* 272 | * Shrink the current window. Find the window that gains space. Hack at the 273 | * window descriptions. Ask the redisplay to do all the hard work 274 | */ 275 | int shrinkwind (int f, int n) 276 | { 277 | WINDOW *adjwp; 278 | LINE *lp; 279 | int i; 280 | 281 | if (n < 0) 282 | return (enlargewind (f, -n)); 283 | if (wheadp->w_wndp == NULL) 284 | { 285 | mlwrite ("Only one window"); 286 | return (FALSE); 287 | } 288 | if ((adjwp = curwp->w_wndp) == NULL) 289 | { 290 | adjwp = wheadp; 291 | while (adjwp->w_wndp != curwp) 292 | adjwp = adjwp->w_wndp; 293 | } 294 | if (curwp->w_ntrows <= n) 295 | { 296 | mlwrite ("Impossible change"); 297 | return (FALSE); 298 | } 299 | if (curwp->w_wndp == adjwp) 300 | { /* Grow below */ 301 | lp = adjwp->w_linep; 302 | for (i = 0; i < n && lback (lp) != adjwp->w_bufp->b_linep; ++i) 303 | lp = lback (lp); 304 | adjwp->w_linep = lp; 305 | adjwp->w_toprow -= n; 306 | } 307 | else 308 | { /* Grow above */ 309 | lp = curwp->w_linep; 310 | for (i = 0; i < n && lp != curbp->b_linep; ++i) 311 | lp = lforw (lp); 312 | curwp->w_linep = lp; 313 | curwp->w_toprow += n; 314 | } 315 | curwp->w_ntrows -= n; 316 | adjwp->w_ntrows += n; 317 | curwp->w_flag |= WFMODE | WFHARD; 318 | adjwp->w_flag |= WFMODE | WFHARD; 319 | return (TRUE); 320 | } 321 | 322 | /* 323 | * Pick a window for a pop-up. Split the screen if there is only one window. 324 | * Pick the uppermost window that isn't the current window. An LRU algorithm 325 | * might be better. Return a pointer, or NULL on error 326 | */ 327 | WINDOW* wpopup () 328 | { 329 | WINDOW *wp; 330 | 331 | if (wheadp->w_wndp == NULL /* Only 1 window */ 332 | && splitwind (FALSE, 0) == FALSE) /* and it won't split */ 333 | return (NULL); 334 | wp = wheadp; /* Find window to use */ 335 | while (wp != NULL && wp == curwp) 336 | wp = wp->w_wndp; 337 | return (wp); 338 | } 339 | 340 | /* 341 | * scroll the next window up (back) a page 342 | */ 343 | int scrnextup (int f, int n) 344 | { 345 | nextwind (FALSE, 1); 346 | backpage (f, n); 347 | return (prevwind (FALSE, 1)); 348 | } 349 | 350 | /* 351 | * scroll the next window down (forward) a page 352 | */ 353 | int scrnextdw (int f, int n) 354 | { 355 | nextwind (FALSE, 1); 356 | forwpage (f, n); 357 | return (prevwind (FALSE, 1)); 358 | } 359 | -------------------------------------------------------------------------------- /word.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The routines in this file implement commands that work word at a time. 3 | * There are all sorts of word mode commands. If I do any sentence and/or 4 | * paragraph mode commands, they are likely to be put in this file 5 | */ 6 | 7 | #include "estruct.h" 8 | #include "edef.h" 9 | 10 | extern int backchar (int f, int n); 11 | extern int forwchar (int f, int n); 12 | extern void lchange (int flag); 13 | extern int ldelete (int n, int kflag); 14 | extern void mlwrite (); 15 | extern int gotobop (int f, int n); 16 | extern int gotoeop (int f, int n); 17 | extern int linsert (int n, int c); 18 | extern int lnewline (); 19 | 20 | int backword (int f, int n); 21 | int forwword (int f, int n); 22 | int upperword (int f, int n); 23 | int lowerword (int f, int n); 24 | int capword (int f, int n); 25 | int delfword (int f, int n); 26 | int delbword (int f, int n); 27 | int inword (); 28 | int fillpara (int f, int n); 29 | 30 | /* 31 | * Move the cursor backward by "n" words. All of the details of motion are 32 | * performed by the "backchar" and "forwchar" routines. Error if you try to 33 | * move beyond the buffers 34 | */ 35 | int backword (int f, int n) 36 | { 37 | if (n < 0) 38 | return (forwword (f, -n)); 39 | if (backchar (FALSE, 1) == FALSE) 40 | return (FALSE); 41 | while (n--) 42 | { 43 | while (inword () == FALSE) 44 | { 45 | if (backchar (FALSE, 1) == FALSE) 46 | return (FALSE); 47 | } 48 | while (inword () != FALSE) 49 | { 50 | if (backchar (FALSE, 1) == FALSE) 51 | return (FALSE); 52 | } 53 | } 54 | return (forwchar (FALSE, 1)); 55 | } 56 | 57 | /* 58 | * Move the cursor forward by the specified number of words. All of the motion 59 | * is done by "forwchar". Error if you try and move beyond the buffer's end 60 | */ 61 | int forwword (int f, int n) 62 | { 63 | if (n < 0) 64 | return (backword (f, -n)); 65 | while (n--) 66 | { 67 | while (inword () != FALSE) 68 | { 69 | if (forwchar (FALSE, 1) == FALSE) 70 | return (FALSE); 71 | } 72 | while (inword () == FALSE) 73 | { 74 | if (forwchar (FALSE, 1) == FALSE) 75 | return (FALSE); 76 | } 77 | } 78 | return (TRUE); 79 | } 80 | 81 | /* 82 | * Move the cursor forward by the specified number of words. As you move, 83 | * convert any characters to upper case. Error if you try and move beyond the 84 | * end of the buffer. Bound to "M-U" 85 | */ 86 | int upperword (int f, int n) 87 | { 88 | int c; 89 | 90 | if (n < 0) 91 | return (FALSE); 92 | while (n--) 93 | { 94 | while (inword () == FALSE) 95 | { 96 | if (forwchar (FALSE, 1) == FALSE) 97 | return (FALSE); 98 | } 99 | while (inword () != FALSE) 100 | { 101 | c = lgetc (curwp->w_dotp, curwp->w_doto); 102 | if (c >= 'a' && c <= 'z') 103 | { 104 | c -= 'a' - 'A'; 105 | lputc (curwp->w_dotp, curwp->w_doto, c); 106 | lchange (WFHARD); 107 | } 108 | if (forwchar (FALSE, 1) == FALSE) 109 | return (FALSE); 110 | } 111 | } 112 | return (TRUE); 113 | } 114 | 115 | /* 116 | * Move the cursor forward by the specified number of words. As you move 117 | * convert characters to lower case. Error if you try and move over the end of 118 | * the buffer. Bound to "M-L" 119 | */ 120 | int lowerword (int f, int n) 121 | { 122 | int c; 123 | 124 | if (n < 0) 125 | return (FALSE); 126 | while (n--) 127 | { 128 | while (inword () == FALSE) 129 | { 130 | if (forwchar (FALSE, 1) == FALSE) 131 | return (FALSE); 132 | } 133 | while (inword () != FALSE) 134 | { 135 | c = lgetc (curwp->w_dotp, curwp->w_doto); 136 | if (c >= 'A' && c <= 'Z') 137 | { 138 | c += 'a' - 'A'; 139 | lputc (curwp->w_dotp, curwp->w_doto, c); 140 | lchange (WFHARD); 141 | } 142 | if (forwchar (FALSE, 1) == FALSE) 143 | return (FALSE); 144 | } 145 | } 146 | return (TRUE); 147 | } 148 | 149 | /* 150 | * Move the cursor forward by the specified number of words. As you move 151 | * convert the first character of the word to upper case, and subsequent 152 | * characters to lower case. Error if you try and move past the end of the 153 | * buffer. Bound to "M-C" 154 | */ 155 | int capword (int f, int n) 156 | { 157 | int c; 158 | 159 | if (n < 0) 160 | return (FALSE); 161 | while (n--) 162 | { 163 | while (inword () == FALSE) 164 | { 165 | if (forwchar (FALSE, 1) == FALSE) 166 | return (FALSE); 167 | } 168 | if (inword () != FALSE) 169 | { 170 | c = lgetc (curwp->w_dotp, curwp->w_doto); 171 | if (c >= 'a' && c <= 'z') 172 | { 173 | c -= 'a' - 'A'; 174 | lputc (curwp->w_dotp, curwp->w_doto, c); 175 | lchange (WFHARD); 176 | } 177 | if (forwchar (FALSE, 1) == FALSE) 178 | return (FALSE); 179 | while (inword () != FALSE) 180 | { 181 | c = lgetc (curwp->w_dotp, curwp->w_doto); 182 | if (c >= 'A' && c <= 'Z') 183 | { 184 | c += 'a' - 'A'; 185 | lputc (curwp->w_dotp, curwp->w_doto, c); 186 | lchange (WFHARD); 187 | } 188 | if (forwchar (FALSE, 1) == FALSE) 189 | return (FALSE); 190 | } 191 | } 192 | } 193 | return (TRUE); 194 | } 195 | 196 | /* 197 | * Kill forward by "n" words. Remember the location of dot. Move forward by 198 | * the right number of words. Put dot back where it was and issue the kill 199 | * command for the right number of characters. Bound to "M-D" 200 | */ 201 | int delfword (int f, int n) 202 | { 203 | LINE *dotp; 204 | int size, doto; 205 | 206 | if (n < 0) 207 | return (FALSE); 208 | dotp = curwp->w_dotp; 209 | doto = curwp->w_doto; 210 | size = 0; 211 | while (n--) 212 | { 213 | while (inword () != FALSE) 214 | { 215 | if (forwchar (FALSE, 1) == FALSE) 216 | return (FALSE); 217 | ++size; 218 | } 219 | while (inword () == FALSE) 220 | { 221 | if (forwchar (FALSE, 1) == FALSE) 222 | return (FALSE); 223 | ++size; 224 | } 225 | } 226 | curwp->w_dotp = dotp; 227 | curwp->w_doto = doto; 228 | return (ldelete (size, TRUE)); 229 | } 230 | 231 | /* 232 | * Kill backwards by "n" words. Move backwards by the desired number of words, 233 | * counting the characters. When dot is finally moved to its resting place, 234 | * fire off the kill command. Bound to "M-Rubout" and to "M-Backspace" 235 | */ 236 | int delbword (int f, int n) 237 | { 238 | int size; 239 | 240 | if (n < 0) 241 | return (FALSE); 242 | if (backchar (FALSE, 1) == FALSE) 243 | return (FALSE); 244 | size = 0; 245 | while (n--) 246 | { 247 | while (inword () == FALSE) 248 | { 249 | if (backchar (FALSE, 1) == FALSE) 250 | return (FALSE); 251 | ++size; 252 | } 253 | while (inword () != FALSE) 254 | { 255 | if (backchar (FALSE, 1) == FALSE) 256 | return (FALSE); 257 | ++size; 258 | } 259 | } 260 | if (forwchar (FALSE, 1) == FALSE) 261 | return (FALSE); 262 | return (ldelete (size, TRUE)); 263 | } 264 | 265 | /* 266 | * Return TRUE if the character at dot is a character that is considered to be 267 | * part of a word. The word character list is hard coded. Should be setable 268 | */ 269 | int inword () 270 | { 271 | int c; 272 | 273 | if (curwp->w_doto == llength (curwp->w_dotp)) 274 | return (FALSE); 275 | c = lgetc (curwp->w_dotp, curwp->w_doto); 276 | if (c >= 'a' && c <= 'z') 277 | return (TRUE); 278 | if (c >= 'A' && c <= 'Z') 279 | return (TRUE); 280 | if (c >= '0' && c <= '9') 281 | return (TRUE); 282 | if (c == '$' || c == '_') /* For identifiers */ 283 | return (TRUE); 284 | return (FALSE); 285 | } 286 | 287 | /* Fill the current paragraph according to the current fill column 288 | */ 289 | int fillpara (int f, int n) 290 | { 291 | LINE *eopline; /* pointer to line just past EOP */ 292 | char wbuf[NLINE]; /* buffer for current word */ 293 | int c; /* current char during scan */ 294 | int wordlen; /* length of current word */ 295 | int clength; /* position on line during fill */ 296 | int i; /* index during word copy */ 297 | int newlength; /* tentative new line length */ 298 | int eopflag; /* Are we at the End-Of-Paragraph? */ 299 | int firstflag; /* first word? (needs no space) */ 300 | 301 | if (fillcol == 0) 302 | { /* no fill column set */ 303 | mlwrite ("No fill column set"); 304 | return (FALSE); 305 | } 306 | /* record the pointer to the line just past the EOP */ 307 | gotoeop (FALSE, 1); 308 | eopline = lforw (curwp->w_dotp); 309 | 310 | /* and back top the begining of the paragraph */ 311 | gotobop (FALSE, 1); 312 | 313 | /* initialize various info */ 314 | clength = curwp->w_doto; 315 | if (clength && curwp->w_dotp->l_text[0] == TAB) 316 | clength = 8; 317 | wordlen = 0; 318 | 319 | /* scan through lines, filling words */ 320 | firstflag = TRUE; 321 | eopflag = FALSE; 322 | while (!eopflag) 323 | { 324 | /* get the next character in the paragraph */ 325 | if (curwp->w_doto == llength (curwp->w_dotp)) 326 | { 327 | c = ' '; 328 | if (lforw (curwp->w_dotp) == eopline) 329 | eopflag = TRUE; 330 | } 331 | else 332 | c = lgetc (curwp->w_dotp, curwp->w_doto); 333 | 334 | ldelete (1, FALSE); /* and then delete it */ 335 | 336 | /* if not a separator, just add it in */ 337 | if (c != ' ' && c != ' ') 338 | { 339 | if (wordlen < (NLINE - 1)) 340 | wbuf[wordlen++] = c; 341 | } 342 | else if (wordlen) 343 | { 344 | /* at a word break with a word waiting */ 345 | /* calculate tantitive new length with word added */ 346 | newlength = clength + 1 + wordlen; 347 | if (newlength <= fillcol) 348 | { 349 | /* add word to current line */ 350 | if (!firstflag) 351 | { 352 | linsert (1, ' '); /* the space */ 353 | ++clength; 354 | } 355 | firstflag = FALSE; 356 | } 357 | else 358 | { 359 | lnewline (); /* start a new line */ 360 | clength = 0; 361 | } 362 | 363 | /* and add the word in in either case */ 364 | for (i = 0; i < wordlen; i++) 365 | { 366 | linsert (1, wbuf[i]); 367 | ++clength; 368 | } 369 | wordlen = 0; 370 | } 371 | } 372 | /* and add a last newline for the end of our new paragraph */ 373 | return (lnewline ()); 374 | } 375 | -------------------------------------------------------------------------------- /basic.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The routines in this file move the cursor around on the screen. They 3 | * compute a new value for the cursor, then adjust ".". The display code 4 | * always updates the cursor location, so only moves between lines, or 5 | * functions that adjust the top line in the window and invalidate the 6 | * framing, are hard. 7 | */ 8 | 9 | #include "estruct.h" 10 | #include "edef.h" 11 | 12 | extern int getccol (int bflg); 13 | extern int inword (); 14 | extern void mlwrite (); 15 | 16 | int forwchar (int f, int n); 17 | int backchar (int f, int n); 18 | int forwline (int f, int n); 19 | int backline (int f, int n); 20 | int gotobop (int f, int n); 21 | int gotoeop (int f, int n); 22 | int forwpage (int f, int n); 23 | int backpage (int f, int n); 24 | 25 | /* 26 | * This routine, given a pointer to a LINE, and the current cursor goal 27 | * column, return the best choice for the offset. The offset is returned. 28 | * Used by "C-N" and "C-P". 29 | */ 30 | long getgoal (LINE *dlp) 31 | { 32 | int col, newcol, dbo, c; 33 | 34 | col = 0; 35 | dbo = 0; 36 | while (dbo != llength (dlp)) 37 | { 38 | c = lgetc (dlp, dbo); 39 | newcol = col; 40 | if (c == '\t') 41 | newcol |= 0x07; 42 | else if (c < 0x20 || c == 0x7F) 43 | ++newcol; 44 | ++newcol; 45 | if (newcol > curgoal) 46 | break; 47 | col = newcol; 48 | ++dbo; 49 | } 50 | return (dbo); 51 | } 52 | 53 | /* 54 | * Move the cursor to the 55 | * beginning of the current line. 56 | * Trivial. 57 | */ 58 | /* ARGSUSED0 */ 59 | int gotobol (int f, int n) 60 | { 61 | curwp->w_doto = 0; 62 | return (TRUE); 63 | } 64 | 65 | /* 66 | * Move the cursor to the end of the current line. Trivial. No errors. 67 | */ 68 | /* ARGSUSED0 */ 69 | int gotoeol (int f, int n) 70 | { 71 | curwp->w_doto = llength (curwp->w_dotp); 72 | return (TRUE); 73 | } 74 | 75 | /* 76 | * Move the cursor backwards by "n" characters. If "n" is less than zero call 77 | * "forwchar" to actually do the move. Otherwise compute the new cursor 78 | * location. Error if you try and move out of the buffer. Set the flag if the 79 | * line pointer for dot changes. 80 | */ 81 | int backchar (int f, int n) 82 | { 83 | LINE *lp; 84 | 85 | if (n < 0) 86 | return (forwchar (f, -n)); 87 | while (n--) 88 | { 89 | if (curwp->w_doto == 0) 90 | { 91 | if ((lp = lback (curwp->w_dotp)) == curbp->b_linep) 92 | return (FALSE); 93 | curwp->w_dotp = lp; 94 | curwp->w_doto = llength (lp); 95 | curwp->w_flag |= WFMOVE; 96 | } 97 | else 98 | curwp->w_doto--; 99 | } 100 | return (TRUE); 101 | } 102 | 103 | /* 104 | * Move the cursor forwards by "n" characters. If "n" is less than zero call 105 | * "backchar" to actually do the move. Otherwise compute the new cursor 106 | * location, and move ".". Error if you try and move off the end of the 107 | * buffer. Set the flag if the line pointer for dot changes. 108 | */ 109 | int forwchar (int f, int n) 110 | { 111 | if (n < 0) 112 | return (backchar (f, -n)); 113 | while (n--) 114 | { 115 | if (curwp->w_doto == llength (curwp->w_dotp)) 116 | { 117 | if (curwp->w_dotp == curbp->b_linep) 118 | return (FALSE); 119 | curwp->w_dotp = lforw (curwp->w_dotp); 120 | curwp->w_doto = 0; 121 | curwp->w_flag |= WFMOVE; 122 | } 123 | else 124 | curwp->w_doto++; 125 | } 126 | return (TRUE); 127 | } 128 | 129 | /* move to a particular line. argument (n) must be a positive integer for this 130 | * to actually do anything 131 | */ 132 | int gotoline (int f, int n) 133 | { 134 | if (n < 1) /* if a bogus argument...then leave */ 135 | return (FALSE); 136 | 137 | /* first, we go to the start of the buffer */ 138 | curwp->w_dotp = lforw (curbp->b_linep); 139 | curwp->w_doto = 0; 140 | return (forwline (f, n - 1)); 141 | } 142 | 143 | /* 144 | * Goto the beginning of the buffer. Massive adjustment of dot. This is 145 | * considered to be hard motion; it really isn't if the original value of dot 146 | * is the same as the new value of dot. Normally bound to "M-<". 147 | */ 148 | /* ARGSUSED0 */ 149 | int gotobob (int f, int n) 150 | { 151 | curwp->w_dotp = lforw (curbp->b_linep); 152 | curwp->w_doto = 0; 153 | curwp->w_flag |= WFHARD; 154 | return (TRUE); 155 | } 156 | 157 | /* 158 | * Move to the end of the buffer. Dot is always put at the end of the file 159 | * (ZJ). The standard screen code does most of the hard parts of update. 160 | * Bound to "M->". 161 | */ 162 | /* ARGSUSED0 */ 163 | int gotoeob (int f, int n) 164 | { 165 | curwp->w_dotp = curbp->b_linep; 166 | curwp->w_doto = 0; 167 | curwp->w_flag |= WFHARD; 168 | return (TRUE); 169 | } 170 | 171 | /* 172 | * Move forward by full lines. If the number of lines to move is less than 173 | * zero, call the backward line function to actually do it. The last command 174 | * controls how the goal column is set. Bound to "C-N". No errors are possible. 175 | */ 176 | int forwline (int f, int n) 177 | { 178 | LINE *dlp; 179 | 180 | if (n < 0) 181 | return (backline (f, -n)); 182 | if ((lastflag & CFCPCN) == 0)/* Reset goal if last */ 183 | curgoal = getccol (FALSE); /* not C-P or C-N */ 184 | thisflag |= CFCPCN; 185 | dlp = curwp->w_dotp; 186 | while (n-- && dlp != curbp->b_linep) 187 | dlp = lforw (dlp); 188 | curwp->w_dotp = dlp; 189 | curwp->w_doto = getgoal (dlp); 190 | curwp->w_flag |= WFMOVE; 191 | return (TRUE); 192 | } 193 | 194 | /* 195 | * This function is like "forwline", but goes backwards. The scheme is exactly 196 | * the same. Check for arguments that are less than zero and call your 197 | * alternate. Figure out the new line and call "movedot" to perform the 198 | * motion. No errors are possible. Bound to "C-P". 199 | */ 200 | int backline (int f, int n) 201 | { 202 | LINE *dlp; 203 | 204 | if (n < 0) 205 | return (forwline (f, -n)); 206 | if ((lastflag & CFCPCN) == 0)/* Reset goal if the */ 207 | curgoal = getccol (FALSE); /* last isn't C-P, C-N */ 208 | thisflag |= CFCPCN; 209 | dlp = curwp->w_dotp; 210 | while (n-- && lback (dlp) != curbp->b_linep) 211 | dlp = lback (dlp); 212 | curwp->w_dotp = dlp; 213 | curwp->w_doto = getgoal (dlp); 214 | curwp->w_flag |= WFMOVE; 215 | return (TRUE); 216 | } 217 | 218 | /* go back to the begining of the current paragraph here we look for a 219 | * or or combination to delimit the begining of 220 | * a paragraph 221 | */ 222 | int gotobop (int f, int n) 223 | { 224 | int suc; /* success of last backchar */ 225 | 226 | if (n < 0) /* the other way.. */ 227 | return (gotoeop (f, -n)); 228 | 229 | while (n-- > 0) 230 | { /* for each one asked for */ 231 | /* first scan back until we are in a word */ 232 | suc = backchar (FALSE, 1); 233 | while (!inword () && suc) 234 | suc = backchar (FALSE, 1); 235 | curwp->w_doto = 0; /* and go to the B-O-Line */ 236 | 237 | /* and scan back until we hit a or or a */ 238 | while (lback (curwp->w_dotp) != curbp->b_linep) 239 | if (llength (curwp->w_dotp) != 0 && 240 | lgetc (curwp->w_dotp, curwp->w_doto) != TAB && 241 | lgetc (curwp->w_dotp, curwp->w_doto) != ' ') 242 | curwp->w_dotp = lback (curwp->w_dotp); 243 | else 244 | break; 245 | 246 | /* and then forward until we are in a word */ 247 | suc = forwchar (FALSE, 1); 248 | while (suc && !inword ()) 249 | suc = forwchar (FALSE, 1); 250 | } 251 | curwp->w_flag |= WFMOVE; /* force screen update */ 252 | return (TRUE); 253 | } 254 | 255 | /* go forword to the end of the current paragraph here we look for a 256 | * or or combination to delimit the begining of a 257 | * paragraph 258 | */ 259 | int gotoeop (int f, int n) 260 | { 261 | int suc; /* success of last backchar */ 262 | 263 | if (n < 0) /* the other way.. */ 264 | return (gotobop (f, -n)); 265 | 266 | while (n-- > 0) 267 | { /* for each one asked for */ 268 | /* first scan forward until we are in a word */ 269 | suc = forwchar (FALSE, 1); 270 | while (!inword () && suc) 271 | suc = forwchar (FALSE, 1); 272 | curwp->w_doto = 0; /* and go to the B-O-Line */ 273 | if (suc) /* of next line if not at EOF */ 274 | curwp->w_dotp = lforw (curwp->w_dotp); 275 | 276 | /* and scan forword until hit a or or a */ 277 | while (curwp->w_dotp != curbp->b_linep) 278 | { 279 | if (llength (curwp->w_dotp) != 0 && 280 | lgetc (curwp->w_dotp, curwp->w_doto) != TAB && 281 | lgetc (curwp->w_dotp, curwp->w_doto) != ' ') 282 | curwp->w_dotp = lforw (curwp->w_dotp); 283 | else 284 | break; 285 | } 286 | 287 | /* and then backward until we are in a word */ 288 | suc = backchar (FALSE, 1); 289 | while (suc && !inword ()) 290 | { 291 | suc = backchar (FALSE, 1); 292 | } 293 | curwp->w_doto = llength (curwp->w_dotp); /* and to the EOL */ 294 | } 295 | curwp->w_flag |= WFMOVE; /* force screen update */ 296 | return (TRUE); 297 | } 298 | 299 | /* 300 | * Scroll forward by a specified number of lines, or by a full page if no 301 | * argument. Bound to "C-V". The "2" in the arithmetic on the window size is 302 | * the overlap; this value is the default overlap value in ITS EMACS. Because 303 | * this zaps the top line in the display window, we have to do a hard update. 304 | */ 305 | int forwpage (int f, int n) 306 | { 307 | LINE *lp; 308 | 309 | if (f == FALSE) 310 | { 311 | n = curwp->w_ntrows - 2; /* Default scroll */ 312 | if (n <= 0) /* Forget the overlap */ 313 | n = 1; /* if tiny window */ 314 | } 315 | else if (n < 0) 316 | return (backpage (f, -n)); 317 | else /* Convert from pages */ 318 | n *= curwp->w_ntrows; /* to lines */ 319 | lp = curwp->w_linep; 320 | while (n-- && lp != curbp->b_linep) 321 | lp = lforw (lp); 322 | curwp->w_linep = lp; 323 | curwp->w_dotp = lp; 324 | curwp->w_doto = 0; 325 | curwp->w_flag |= WFHARD; 326 | return (TRUE); 327 | } 328 | 329 | /* 330 | * This command is like "forwpage", but it goes backwards. The "2", like 331 | * above, is the overlap between the two windows. The value is from the ITS 332 | * EMACS manual. Bound to "M-V". We do a hard update for exactly the same 333 | * reason. 334 | */ 335 | int backpage (int f, int n) 336 | { 337 | LINE *lp; 338 | 339 | if (f == FALSE) 340 | { 341 | n = curwp->w_ntrows - 2; /* Default scroll */ 342 | if (n <= 0) /* Don't blow up if the window is tiny */ 343 | n = 1; 344 | } 345 | else if (n < 0) 346 | return (forwpage (f, -n)); 347 | else /* Convert from pages to lines */ 348 | n *= curwp->w_ntrows; 349 | lp = curwp->w_linep; 350 | while (n-- && lback (lp) != curbp->b_linep) 351 | lp = lback (lp); 352 | curwp->w_linep = lp; 353 | curwp->w_dotp = lp; 354 | curwp->w_doto = 0; 355 | curwp->w_flag |= WFHARD; 356 | return (TRUE); 357 | } 358 | 359 | /* 360 | * Set the mark in the current window to the value of "." in the window. No 361 | * errors are possible. Bound to "M-.". 362 | */ 363 | /* ARGSUSED0 */ 364 | int setmark (int f, int n) 365 | { 366 | curwp->w_markp = curwp->w_dotp; 367 | curwp->w_marko = curwp->w_doto; 368 | mlwrite ("[Mark set]"); 369 | return (TRUE); 370 | } 371 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is in public domain; originally written by Dave G. Conroy. 3 | * This file contains the main driving routine, and some keyboard processing 4 | * code 5 | */ 6 | 7 | #define maindef /* make global definitions not external */ 8 | 9 | #include /* strncpy(3) */ 10 | #include /* malloc(3) */ 11 | #include "estruct.h" /* global structures and defines */ 12 | #include "efunc.h" /* function declarations and name table */ 13 | #include "edef.h" /* global definitions */ 14 | #include "ebind.h" 15 | 16 | extern void getwinsize(); 17 | extern void vtinit (); 18 | extern void vttidy (); 19 | extern void update (); 20 | extern void mlerase (); 21 | extern void mlwrite (); 22 | extern int mlyesno (char *prompt); 23 | extern void makename (char bname[], char fname[]); 24 | extern int readin (char fname[]); 25 | extern int linsert (int f, int n); 26 | extern int anycb (); 27 | extern BUFFER *bfind (); 28 | 29 | int main (int argc, char *argv[]); 30 | void edinit (char bname[]); 31 | int execute (int c, int f, int n); 32 | int getkey (); 33 | int getctl (); 34 | int quickexit (int f, int n); 35 | int quit (int f, int n); 36 | int ctlxlp (int f, int n); 37 | int ctlxrp (int f, int n); 38 | int ctlxe (int f, int n); 39 | int ctrlg (int f, int n); 40 | int extendedcmd (int f, int n); 41 | 42 | int main (int argc, char *argv[]) 43 | { 44 | BUFFER *bp; 45 | char bname[NBUFN]; /* buffer name of file to read */ 46 | int c, f, n, mflag; 47 | int ffile; /* first file flag */ 48 | int carg; /* current arg to scan */ 49 | int basec; /* c stripped of meta character */ 50 | 51 | /* initialize the editor and process the startup file */ 52 | getwinsize(); /* find out the "real" screen size */ 53 | strncpy (bname, "main", 5); /* default buffer name */ 54 | edinit (bname); /* Buffers, windows */ 55 | vtinit (); /* Displays */ 56 | ffile = TRUE; /* no file to edit yet */ 57 | update (); /* let the user know we are here */ 58 | 59 | /* scan through the command line and get the files to edit */ 60 | for (carg = 1; carg < argc; ++carg) 61 | { 62 | /* set up a buffer for this file */ 63 | makename (bname, argv[carg]); 64 | 65 | /* if this is the first file, read it in */ 66 | if (ffile) 67 | { 68 | bp = curbp; 69 | makename (bname, argv[carg]); 70 | strncpy (bp->b_bname, bname, NBUFN); 71 | strncpy (bp->b_fname, argv[carg], NFILEN); 72 | if (readin (argv[carg]) == ABORT) 73 | { 74 | strncpy (bp->b_bname, "main", 5); 75 | strncpy (bp->b_fname, "", 1); 76 | } 77 | bp->b_dotp = bp->b_linep; 78 | bp->b_doto = 0; 79 | ffile = FALSE; 80 | } 81 | else 82 | { 83 | /* set this to inactive */ 84 | bp = bfind (bname, TRUE, 0); 85 | strncpy (bp->b_fname, argv[carg], NFILEN); 86 | bp->b_active = FALSE; 87 | } 88 | } 89 | 90 | /* setup to process commands */ 91 | lastflag = 0; /* Fake last flags */ 92 | curwp->w_flag |= WFMODE; /* and force an update */ 93 | 94 | loop: 95 | update (); /* Fix up the screen */ 96 | c = getkey (); 97 | if (mpresf != FALSE) 98 | { 99 | mlerase (); 100 | update (); 101 | } 102 | f = FALSE; 103 | n = 1; 104 | 105 | /* do META-# processing if needed */ 106 | 107 | basec = c & ~META; /* strip meta char off if there */ 108 | if ((c & META) && ((basec >= '0' && basec <= '9') || basec == '-')) 109 | { 110 | f = TRUE; /* there is a # arg */ 111 | n = 0; /* start with a zero default */ 112 | mflag = 1; /* current minus flag */ 113 | c = basec; /* strip the META */ 114 | while ((c >= '0' && c <= '9') || (c == '-')) 115 | { 116 | if (c == '-') 117 | { 118 | /* already hit a minus or digit? */ 119 | if ((mflag == -1) || (n != 0)) 120 | break; 121 | mflag = -1; 122 | } 123 | else 124 | n = n * 10 + (c - '0'); 125 | if ((n == 0) && (mflag == -1)) /* lonely - */ 126 | mlwrite ("Arg:"); 127 | else 128 | mlwrite ("Arg: %d", n * mflag); 129 | 130 | c = getkey (); /* get the next key */ 131 | } 132 | n = n * mflag; /* figure in the sign */ 133 | } 134 | /* do ^U repeat argument processing */ 135 | 136 | if (c == (CTRL | 'U')) 137 | { /* ^U, start argument */ 138 | f = TRUE; 139 | n = 4; /* with argument of 4 */ 140 | mflag = 0; /* that can be discarded */ 141 | mlwrite ("Arg: 4"); 142 | while (((c = getkey ()) >= '0') 143 | && ((c <= '9') || (c == (CTRL | 'U')) || (c == '-'))) 144 | { 145 | if (c == (CTRL | 'U')) 146 | n = n * 4; 147 | /* 148 | * If dash, and start of argument string, set arg. 149 | * to -1. Otherwise, insert it. 150 | */ 151 | else if (c == '-') 152 | { 153 | if (mflag) 154 | break; 155 | n = 0; 156 | mflag = -1; 157 | } 158 | /* 159 | * If first digit entered, replace previous argument 160 | * with digit and set sign. Otherwise, append to arg. 161 | */ 162 | else 163 | { 164 | if (!mflag) 165 | { 166 | n = 0; 167 | mflag = 1; 168 | } 169 | n = 10 * n + c - '0'; 170 | } 171 | mlwrite ("Arg: %d", (mflag >= 0) ? n : (n ? -n : -1)); 172 | } 173 | /* 174 | * Make arguments preceded by a minus sign negative and change 175 | * the special argument "^U -" to an effective "^U -1". 176 | */ 177 | if (mflag == -1) 178 | { 179 | if (n == 0) 180 | n++; 181 | n = -n; 182 | } 183 | } 184 | 185 | if (c == (CTRL | 'X')) /* ^X is a prefix */ 186 | c = CTLX | getctl (); 187 | if (kbdmip != NULL) 188 | { /* Save macro strokes */ 189 | if (c != (CTLX | ')') && kbdmip > &kbdm[NKBDM - 6]) 190 | { 191 | ctrlg (FALSE, 0); 192 | goto loop; 193 | } 194 | if (f != FALSE) 195 | { 196 | *kbdmip++ = (CTRL | 'U'); 197 | *kbdmip++ = n; 198 | } 199 | *kbdmip++ = c; 200 | } 201 | execute (c, f, n); /* Do it */ 202 | goto loop; 203 | } 204 | 205 | /* 206 | * Initialize all of the buffers and windows. The buffer name is passed down 207 | * as an argument, because the main routine may have been told to read in a 208 | * file by default, and we want the buffer name to be right. 209 | */ 210 | void edinit (char bname[]) 211 | { 212 | BUFFER *bp; 213 | WINDOW *wp; 214 | 215 | bp = bfind (bname, TRUE, 0); /* First buffer */ 216 | blistp = bfind ("[List]", TRUE, BFTEMP); /* Buffer list buffer */ 217 | wp = (WINDOW *) malloc (sizeof (WINDOW)); /* First window */ 218 | if (bp == NULL || wp == NULL || blistp == NULL) 219 | exit (1); 220 | curbp = bp; /* Make this current */ 221 | wheadp = wp; 222 | curwp = wp; 223 | wp->w_wndp = NULL; /* Initialize window */ 224 | wp->w_bufp = bp; 225 | bp->b_nwnd = 1; /* Displayed */ 226 | wp->w_linep = bp->b_linep; 227 | wp->w_dotp = bp->b_linep; 228 | wp->w_doto = 0; 229 | wp->w_markp = NULL; 230 | wp->w_marko = 0; 231 | wp->w_toprow = 0; 232 | wp->w_ntrows = term.t_nrow - 1; /* "-1" for mode line */ 233 | wp->w_force = 0; 234 | wp->w_flag = WFMODE | WFHARD; /* Full */ 235 | } 236 | 237 | /* 238 | * This is the general command execution routine. It handles the fake binding 239 | * of all the keys to "self-insert". It also clears out the "thisflag" word, 240 | * and arranges to move it to the "lastflag", so that the next command can 241 | * look at it. Return the status of command. 242 | */ 243 | int execute (int c, int f, int n) 244 | { 245 | KEYTAB *ktp; 246 | int status; 247 | 248 | ktp = &keytab[0]; /* Look in key table */ 249 | while (ktp->k_fp != NULL) 250 | { 251 | if (ktp->k_code == c) 252 | { 253 | thisflag = 0; 254 | status = (*ktp->k_fp) (f, n); 255 | lastflag = thisflag; 256 | return (status); 257 | } 258 | ++ktp; 259 | } 260 | 261 | if ((c >= 0x20 && c <= 0x7E) /* Self inserting */ 262 | || (c >= 0xA0 && c <= 0xFE)) 263 | { 264 | if (n <= 0) 265 | { /* Fenceposts */ 266 | lastflag = 0; 267 | return (n < 0 ? FALSE : TRUE); 268 | } 269 | thisflag = 0; /* For the future */ 270 | 271 | status = linsert (n, c); 272 | 273 | lastflag = thisflag; 274 | return (status); 275 | } 276 | mlwrite ("\007[Key not bound]"); /* complain */ 277 | lastflag = 0; /* Fake last flags */ 278 | return (FALSE); 279 | } 280 | 281 | /* 282 | * Read in a key. Do the standard keyboard preprocessing. Convert the keys to 283 | * the internal character set. 284 | */ 285 | int getkey () 286 | { 287 | int c; 288 | 289 | c = (*term.t_getchar) (); 290 | 291 | if (c == METACH) 292 | { /* Apply M- prefix */ 293 | c = getctl (); 294 | return (META | c); 295 | } 296 | if (c >= 0x00 && c <= 0x1F) /* C0 control -> C- */ 297 | c = CTRL | (c + '@'); 298 | return (c); 299 | } 300 | 301 | /* 302 | * Get a key. Apply control modifications to the read key. 303 | */ 304 | int getctl () 305 | { 306 | int c; 307 | 308 | c = (*term.t_getchar) (); 309 | if (c >= 'a' && c <= 'z') /* Force to upper */ 310 | c -= 0x20; 311 | if (c >= 0x00 && c <= 0x1F) /* C0 control -> C- */ 312 | c = CTRL | (c + '@'); 313 | return (c); 314 | } 315 | 316 | /* 317 | * Fancy quit command, as implemented by Norm. If the any buffer has changed 318 | * do a write on that buffer and exit emacs, otherwise simply exit. 319 | */ 320 | int quickexit (int f, int n) 321 | { 322 | BUFFER *bp; /* scanning pointer to buffers */ 323 | 324 | bp = bheadp; 325 | while (bp != NULL) 326 | { 327 | if ((bp->b_flag & BFCHG) != 0 /* Changed */ 328 | && (bp->b_flag & BFTEMP) == 0) 329 | { /* Real */ 330 | curbp = bp; /* make that buffer current */ 331 | mlwrite ("[Saving %s]", (int*)bp->b_fname); 332 | filesave (f, n); 333 | } 334 | bp = bp->b_bufp; /* on to the next buffer */ 335 | } 336 | return quit (f, n); /* conditionally quit */ 337 | } 338 | 339 | /* 340 | * Quit command. If an argument, always quit. Otherwise confirm if a buffer 341 | * has been changed and not written out. Normally bound to "C-X C-C". 342 | */ 343 | int quit (int f, int n) 344 | { 345 | int s; 346 | 347 | if (f != FALSE /* Argument forces it */ 348 | || anycb () == FALSE /* All buffers clean */ 349 | || (s = mlyesno ("Modified buffers exist. Leave anyway")) == TRUE) 350 | { 351 | vttidy (); 352 | exit (0); 353 | } 354 | mlwrite (""); 355 | return (s); 356 | } 357 | 358 | /* 359 | * Begin a keyboard macro. Error if not at the top level in keyboard 360 | * processing. Set up variables and return. 361 | */ 362 | int ctlxlp (int f, int n) 363 | { 364 | if (kbdmip != NULL || kbdmop != NULL) 365 | { 366 | mlwrite ("Not now"); 367 | return (FALSE); 368 | } 369 | mlwrite ("[Start macro]"); 370 | kbdmip = &kbdm[0]; 371 | return (TRUE); 372 | } 373 | 374 | /* 375 | * End keyboard macro. Check for the same limit conditions as the above 376 | * routine. Set up the variables and return to the caller. 377 | */ 378 | int ctlxrp (int f, int n) 379 | { 380 | if (kbdmip == NULL) 381 | { 382 | mlwrite ("Not now"); 383 | return (FALSE); 384 | } 385 | mlwrite ("[End macro]"); 386 | kbdmip = NULL; 387 | return (TRUE); 388 | } 389 | 390 | /* 391 | * Execute a macro. The command argument is the number of times to loop. Quit 392 | * as soon as a command gets an error. Return TRUE if all ok, else FALSE. 393 | */ 394 | int ctlxe (int f, int n) 395 | { 396 | int c, af, an, s; 397 | 398 | if (kbdmip != NULL || kbdmop != NULL) 399 | { 400 | mlwrite ("No macro defined"); 401 | return (FALSE); 402 | } 403 | if (n <= 0) 404 | return (TRUE); 405 | do 406 | { 407 | kbdmop = &kbdm[0]; 408 | do 409 | { 410 | af = FALSE; 411 | an = 1; 412 | if ((c = *kbdmop++) == (CTRL | 'U')) 413 | { 414 | af = TRUE; 415 | an = *kbdmop++; 416 | c = *kbdmop++; 417 | } 418 | s = TRUE; 419 | } 420 | while (c != (CTLX | ')') && (s = execute (c, af, an)) == TRUE); 421 | kbdmop = NULL; 422 | } 423 | while (s == TRUE && --n); 424 | return (s); 425 | } 426 | 427 | /* 428 | * Abort. Beep the beeper. Kill off any keyboard macro, etc., that is in 429 | * progress. Sometimes called as a routine, to do general aborting of stuff. 430 | */ 431 | int ctrlg (int f, int n) 432 | { 433 | (*term.t_beep) (); 434 | if (kbdmip != NULL) 435 | { 436 | kbdm[0] = (CTLX | ')'); 437 | kbdmip = NULL; 438 | } 439 | mlwrite ("[Aborted]"); 440 | return (ABORT); 441 | } 442 | 443 | /* 444 | * Handle ANSI escape-extended commands (with "ESC [" or "ESC O" prefix) 445 | */ 446 | int extendedcmd (int f, int n) 447 | { 448 | int (*cmd)(); 449 | int c; 450 | 451 | c = getctl(); 452 | switch (c) 453 | { 454 | case 'A': cmd = backline; break; 455 | case 'B': cmd = forwline; break; 456 | case 'C': cmd = forwchar; break; 457 | case 'D': cmd = backchar; break; 458 | case 'H': cmd = gotobob; break; 459 | case 'W': cmd = gotoeob; break; 460 | case '5': cmd = backpage; getctl(); break; 461 | case '6': cmd = forwpage; getctl(); break; 462 | case '7': cmd = gotobob; getctl(); break; 463 | case '8': cmd = gotoeob; getctl(); break; 464 | default: mlwrite ("\007[Key not bound]"); 465 | return (FALSE); 466 | } 467 | return cmd(f, n); 468 | } 469 | -------------------------------------------------------------------------------- /file.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The routines in this file handle the reading and writing of disk files. 3 | * All details about the reading and writing of the disk are in "fileio.c" 4 | */ 5 | 6 | #include /* strncpy(3) */ 7 | #include "estruct.h" 8 | #include "edef.h" 9 | 10 | extern int mlreply (char *prompt, char *buf, int nbuf); 11 | extern int swbuffer (BUFFER *bp); 12 | extern void mlwrite (); 13 | extern int bclear (BUFFER *bp); 14 | extern int ffropen (char *fn); 15 | extern int ffgetline (char buf[], int nbuf); 16 | extern int ffwopen (char *fn); 17 | extern int ffclose (); 18 | extern int ffputline (char buf[], int nbuf); 19 | extern BUFFER *bfind (); 20 | extern LINE *lalloc (); 21 | 22 | int fileread (int f, int n); 23 | int insfile (int f, int n); 24 | int filefind (int f, int n); 25 | int getfile (char fname[]); 26 | int readin (char fname[]); 27 | void makename (char bname[], char fname[]); 28 | int filewrite (int f, int n); 29 | int filesave (int f, int n); 30 | int writeout (char *fn); 31 | int filename (int f, int n); 32 | int ifile (char fname[]); 33 | 34 | /* 35 | * Read a file into the current buffer. This is really easy; all you do it 36 | * find the name of the file, and call the standard "read a file into the 37 | * current buffer" code. Bound to "C-X C-R" 38 | */ 39 | int fileread (int f, int n) 40 | { 41 | int s; 42 | char fname[NFILEN]; 43 | 44 | if ((s = mlreply ("Read file: ", fname, NFILEN)) != TRUE) 45 | return (s); 46 | return (readin (fname)); 47 | } 48 | 49 | /* 50 | * Insert a file into the current buffer. This is really easy; all you do it 51 | * find the name of the file, and call the standard "insert a file into the 52 | * current buffer" code. Bound to "C-X C-I". 53 | */ 54 | int insfile (int f, int n) 55 | { 56 | int s; 57 | char fname[NFILEN]; 58 | 59 | if ((s = mlreply ("Insert file: ", fname, NFILEN)) != TRUE) 60 | return (s); 61 | return (ifile (fname)); 62 | } 63 | 64 | /* 65 | * Select a file for editing. Look around to see if you can find the fine in 66 | * another buffer; if you can find it just switch to the buffer. If you cannot 67 | * find the file, create a new buffer, read in the text, and switch to the new 68 | * buffer. Bound to C-X C-F. 69 | */ 70 | int filefind (int f, int n) 71 | { 72 | char fname[NFILEN]; /* file user wishes to find */ 73 | int s; /* status return */ 74 | 75 | if ((s = mlreply ("Find file: ", fname, NFILEN)) != TRUE) 76 | return (s); 77 | return (getfile (fname)); 78 | } 79 | 80 | int getfile (char fname[]) 81 | { 82 | BUFFER *bp; 83 | LINE *lp; 84 | char bname[NBUFN]; /* buffer name to put file */ 85 | int i, s; 86 | 87 | for (bp = bheadp; bp != (BUFFER*)0; bp = bp->b_bufp) 88 | { 89 | if ((bp->b_flag & BFTEMP) == 0 && strcmp (bp->b_fname, fname) == 0) 90 | { 91 | if (--curbp->b_nwnd == 0) 92 | { 93 | curbp->b_dotp = curwp->w_dotp; 94 | curbp->b_doto = curwp->w_doto; 95 | curbp->b_markp = curwp->w_markp; 96 | curbp->b_marko = curwp->w_marko; 97 | } 98 | swbuffer (bp); 99 | lp = curwp->w_dotp; 100 | i = curwp->w_ntrows / 2; 101 | while (i-- && lback (lp) != curbp->b_linep) 102 | lp = lback (lp); 103 | curwp->w_linep = lp; 104 | curwp->w_flag |= WFMODE | WFHARD; 105 | mlwrite ("[Old buffer]"); 106 | return (TRUE); 107 | } 108 | } 109 | makename (bname, fname); /* New buffer name */ 110 | while ((bp = bfind (bname, FALSE, 0)) != (BUFFER*)0) 111 | { 112 | s = mlreply ("Buffer name: ", bname, NBUFN); 113 | if (s == ABORT) /* ^G to just quit */ 114 | return (s); 115 | if (s == FALSE) 116 | { /* CR to clobber it */ 117 | makename (bname, fname); 118 | break; 119 | } 120 | } 121 | if (bp == (BUFFER*)0 && (bp = bfind (bname, TRUE, 0)) == (BUFFER*)0) 122 | { 123 | mlwrite ("Cannot create buffer"); 124 | return (FALSE); 125 | } 126 | if (--curbp->b_nwnd == 0) 127 | { /* Undisplay */ 128 | curbp->b_dotp = curwp->w_dotp; 129 | curbp->b_doto = curwp->w_doto; 130 | curbp->b_markp = curwp->w_markp; 131 | curbp->b_marko = curwp->w_marko; 132 | } 133 | curbp = bp; /* Switch to it */ 134 | curwp->w_bufp = bp; 135 | curbp->b_nwnd++; 136 | return (readin (fname)); /* Read it in */ 137 | } 138 | 139 | /* 140 | * Read file "fname" into the current buffer, blowing away any text found 141 | * there. Called by both the read and find commands. Return the final status 142 | * of the read. Also called by the mainline, to read in a file specified on 143 | * the command line as an argument. 144 | */ 145 | int readin (char fname[]) 146 | { 147 | LINE *lp1, *lp2; 148 | WINDOW *wp; 149 | BUFFER *bp; 150 | char line[NLINE]; 151 | int nbytes, nline, s, i; 152 | int lflag; /* any lines longer than allowed? */ 153 | 154 | bp = curbp; /* Cheap */ 155 | if ((s = bclear (bp)) != TRUE) /* Might be old */ 156 | return (s); 157 | bp->b_flag &= ~(BFTEMP | BFCHG); 158 | strncpy (bp->b_fname, fname, NFILEN); 159 | if ((s = ffropen (fname)) == FIOERR) /* Hard file open */ 160 | goto out; 161 | if (s == FIOFNF) 162 | { /* File not found */ 163 | mlwrite ("[New file]"); 164 | goto out; 165 | } 166 | mlwrite ("[Reading file]"); 167 | nline = 0; 168 | lflag = FALSE; 169 | while ((s = ffgetline (line, NLINE)) == FIOSUC || s == FIOLNG) 170 | { 171 | if (s == FIOLNG) 172 | lflag = TRUE; 173 | nbytes = strlen (line); 174 | if ((lp1 = lalloc (nbytes)) == NULL) 175 | { 176 | s = FIOERR; /* Keep message on the display */ 177 | break; 178 | } 179 | lp2 = lback (curbp->b_linep); 180 | lp2->l_fp = lp1; 181 | lp1->l_fp = curbp->b_linep; 182 | lp1->l_bp = lp2; 183 | curbp->b_linep->l_bp = lp1; 184 | for (i = 0; i < nbytes; ++i) 185 | lputc (lp1, i, line[i]); 186 | ++nline; 187 | } 188 | ffclose (); /* Ignore errors */ 189 | if (s == FIOEOF) 190 | { /* Don't zap message! */ 191 | if (nline == 1) 192 | mlwrite ("[Read 1 line]"); 193 | else 194 | mlwrite ("[Read %d lines]", nline); 195 | } 196 | if (lflag) 197 | mlwrite ("[Read %d line(s), Long lines wrapped]", nline); 198 | out: 199 | for (wp = wheadp; wp != NULL; wp = wp->w_wndp) 200 | { 201 | if (wp->w_bufp == curbp) 202 | { 203 | wp->w_linep = lforw (curbp->b_linep); 204 | wp->w_dotp = lforw (curbp->b_linep); 205 | wp->w_doto = 0; 206 | wp->w_markp = NULL; 207 | wp->w_marko = 0; 208 | wp->w_flag |= WFMODE | WFHARD; 209 | } 210 | } 211 | if (s == FIOERR || s == FIOFNF) /* False if error */ 212 | return (FALSE); 213 | return (TRUE); 214 | } 215 | 216 | /* 217 | * Take a file name, and from it fabricate a buffer name. This routine knows 218 | * about the syntax of file names on the target system. I suppose that this 219 | * information could be put in a better place than a line of code. 220 | */ 221 | void makename (char bname[], char fname[]) 222 | { 223 | char *cp1, *cp2; 224 | 225 | cp1 = &fname[0]; 226 | while (*cp1 != 0) 227 | ++cp1; 228 | 229 | while (cp1 != &fname[0] && cp1[-1] != '/') 230 | --cp1; 231 | cp2 = &bname[0]; 232 | while (cp2 != &bname[NBUFN - 1] && *cp1 != 0 && *cp1 != ';') 233 | *cp2++ = *cp1++; 234 | *cp2 = 0; 235 | } 236 | 237 | /* 238 | * Ask for a file name, and write the contents of the current buffer to that 239 | * file. Update the remembered file name and clear the buffer changed flag. 240 | * This handling of file names is different from the earlier versions, and is 241 | * more compatable with Gosling EMACS than with ITS EMACS. Bound to "C-X C-W". 242 | */ 243 | int filewrite (int f, int n) 244 | { 245 | WINDOW *wp; 246 | char fname[NFILEN]; 247 | int s; 248 | 249 | if ((s = mlreply ("Write file: ", fname, NFILEN)) != TRUE) 250 | return (s); 251 | if ((s = writeout (fname)) == TRUE) 252 | { 253 | strncpy (curbp->b_fname, fname, NFILEN); 254 | curbp->b_flag &= ~BFCHG; 255 | wp = wheadp; /* Update mode lines */ 256 | while (wp != NULL) 257 | { 258 | if (wp->w_bufp == curbp) 259 | wp->w_flag |= WFMODE; 260 | wp = wp->w_wndp; 261 | } 262 | } 263 | return (s); 264 | } 265 | 266 | /* 267 | * Save the contents of the current buffer in its associatd file. No nothing 268 | * if nothing has changed (this may be a bug, not a feature). Error if there 269 | * is no remembered file name for the buffer. Bound to "C-X C-S". May get 270 | * called by "C-Z" 271 | */ 272 | int filesave (int f, int n) 273 | { 274 | WINDOW *wp; 275 | int s; 276 | 277 | if ((curbp->b_flag & BFCHG) == 0) /* Return, no changes */ 278 | return (TRUE); 279 | if (curbp->b_fname[0] == 0) 280 | { /* Must have a name */ 281 | mlwrite ("No file name"); 282 | return (FALSE); 283 | } 284 | if ((s = writeout (curbp->b_fname)) == TRUE) 285 | { 286 | curbp->b_flag &= ~BFCHG; 287 | wp = wheadp; /* Update mode lines */ 288 | while (wp != NULL) 289 | { 290 | if (wp->w_bufp == curbp) 291 | wp->w_flag |= WFMODE; 292 | wp = wp->w_wndp; 293 | } 294 | } 295 | return (s); 296 | } 297 | 298 | /* 299 | * This function performs the details of file writing. Uses the file 300 | * management routines in the "fileio.c" package. The number of lines written 301 | * is displayed. Sadly, it looks inside a LINE; provide a macro for this. Most 302 | * of the grief is error checking of some sort. 303 | */ 304 | int writeout (char *fn) 305 | { 306 | LINE *lp; 307 | int nline, s; 308 | 309 | if ((s = ffwopen (fn)) != FIOSUC) /* Open writes message */ 310 | return (FALSE); 311 | mlwrite ("[Writing]"); /* tell us were writing */ 312 | lp = lforw (curbp->b_linep); /* First line */ 313 | nline = 0; /* Number of lines */ 314 | while (lp != curbp->b_linep) 315 | { 316 | if ((s = ffputline (&lp->l_text[0], llength (lp))) != FIOSUC) 317 | break; 318 | ++nline; 319 | lp = lforw (lp); 320 | } 321 | if (s == FIOSUC) 322 | { /* No write error */ 323 | s = ffclose (); 324 | if (s == FIOSUC) 325 | { /* No close error */ 326 | if (nline == 1) 327 | mlwrite ("[Wrote 1 line]"); 328 | else 329 | mlwrite ("[Wrote %d lines]", nline); 330 | } 331 | } 332 | else /* ignore close error */ 333 | ffclose (); /* if a write error */ 334 | if (s != FIOSUC) /* some sort of error */ 335 | return (FALSE); 336 | return (TRUE); 337 | } 338 | 339 | /* 340 | * The command allows the user to modify the file name associated with the 341 | * current buffer. It is like the "f" command in UNIX "ed". The operation is 342 | * simple; just zap the name in the BUFFER structure, and mark the windows as 343 | * needing an update. You can type a blank line at the prompt if you wish. 344 | */ 345 | int filename (int f, int n) 346 | { 347 | WINDOW *wp; 348 | char fname[NFILEN]; 349 | int s; 350 | 351 | if ((s = mlreply ("Name: ", fname, NFILEN)) == ABORT) 352 | return (s); 353 | if (s == FALSE) 354 | strncpy (curbp->b_fname, "", 1); 355 | else 356 | strncpy (curbp->b_fname, fname, NFILEN); 357 | wp = wheadp; /* update mode lines */ 358 | while (wp != NULL) 359 | { 360 | if (wp->w_bufp == curbp) 361 | wp->w_flag |= WFMODE; 362 | wp = wp->w_wndp; 363 | } 364 | return (TRUE); 365 | } 366 | 367 | /* 368 | * Insert file "fname" into the current buffer, Called by insert file command. 369 | * Return the final status of the read. 370 | */ 371 | int ifile (char fname[]) 372 | { 373 | LINE *lp0, *lp1, *lp2; 374 | BUFFER *bp; 375 | char line[NLINE]; 376 | int i, s, nbytes, nline; 377 | int lflag; /* any lines longer than allowed? */ 378 | 379 | bp = curbp; /* Cheap */ 380 | bp->b_flag |= BFCHG; /* we have changed */ 381 | bp->b_flag &= ~BFTEMP; /* and are not temporary */ 382 | if ((s = ffropen (fname)) == FIOERR) /* Hard file open */ 383 | goto out; 384 | if (s == FIOFNF) 385 | { /* File not found */ 386 | mlwrite ("[No such file]"); 387 | return (FALSE); 388 | } 389 | mlwrite ("[Inserting file]"); 390 | 391 | /* back up a line and save the mark here */ 392 | curwp->w_dotp = lback (curwp->w_dotp); 393 | curwp->w_doto = 0; 394 | curwp->w_markp = curwp->w_dotp; 395 | curwp->w_marko = 0; 396 | 397 | nline = 0; 398 | lflag = FALSE; 399 | while ((s = ffgetline (line, NLINE)) == FIOSUC || s == FIOLNG) 400 | { 401 | if (s == FIOLNG) 402 | lflag = TRUE; 403 | nbytes = strlen (line); 404 | if ((lp1 = lalloc (nbytes)) == NULL) 405 | { 406 | s = FIOERR; /* keep message on the */ 407 | break; /* display */ 408 | } 409 | lp0 = curwp->w_dotp; /* line previous to insert */ 410 | lp2 = lp0->l_fp; /* line after insert */ 411 | 412 | /* re-link new line between lp0 and lp2 */ 413 | lp2->l_bp = lp1; 414 | lp0->l_fp = lp1; 415 | lp1->l_bp = lp0; 416 | lp1->l_fp = lp2; 417 | 418 | /* and advance and write out the current line */ 419 | curwp->w_dotp = lp1; 420 | for (i = 0; i < nbytes; ++i) 421 | lputc (lp1, i, line[i]); 422 | ++nline; 423 | } 424 | ffclose (); /* Ignore errors */ 425 | curwp->w_markp = lforw (curwp->w_markp); 426 | if (s == FIOEOF) 427 | { /* Don't zap message! */ 428 | if (nline == 1) 429 | mlwrite ("[Inserted 1 line]"); 430 | else 431 | mlwrite ("[Inserted %d lines]", nline); 432 | } 433 | if (lflag) 434 | mlwrite ("[Inserted %d line(s), Long lines wrapped]", nline); 435 | out: 436 | /* advance to the next line and mark the window for changes */ 437 | curwp->w_dotp = lforw (curwp->w_dotp); 438 | curwp->w_flag |= WFHARD; 439 | 440 | /* copy window parameters back to the buffer structure */ 441 | curbp->b_dotp = curwp->w_dotp; 442 | curbp->b_doto = curwp->w_doto; 443 | curbp->b_markp = curwp->w_markp; 444 | curbp->b_marko = curwp->w_marko; 445 | 446 | if (s == FIOERR) /* False if error */ 447 | return (FALSE); 448 | return (TRUE); 449 | } 450 | -------------------------------------------------------------------------------- /buffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Buffer management. Some of the functions are internal, and some are actually 3 | * attached to user keys. Like everyone else, they set hints for the display 4 | * system. 5 | */ 6 | 7 | #include /* free(3), malloc(3) */ 8 | #include /* strncpy(3) */ 9 | #include "estruct.h" 10 | #include "edef.h" 11 | 12 | extern int mlreply (char *prompt, char *buf, int nbuf); 13 | extern int readin (char fname[]); 14 | extern void mlwrite (); 15 | extern void mlerase (); 16 | extern int mlyesno (char *prompt); 17 | extern void lfree (LINE *lp); 18 | extern WINDOW *wpopup (); 19 | extern LINE *lalloc (); 20 | 21 | int swbuffer (BUFFER *bp); 22 | int usebuffer (int f, int n); 23 | int nextbuffer (int f, int n); 24 | int killbuffer (int f, int n); 25 | int zotbuf (BUFFER *bp); 26 | int namebuffer (int f, int n); 27 | int listbuffers (int f, int n); 28 | int makelist (); 29 | void itoa (char buf[], int width, int num); 30 | int addline (char *text); 31 | int anycb (); 32 | BUFFER* bfind (char *bname, int cflag, int bflag); 33 | int bclear (BUFFER *bp); 34 | 35 | /* 36 | * make buffer BP current 37 | */ 38 | int swbuffer (BUFFER *bp) 39 | { 40 | WINDOW *wp; 41 | 42 | if (--curbp->b_nwnd == 0) 43 | { /* Last use. */ 44 | curbp->b_dotp = curwp->w_dotp; 45 | curbp->b_doto = curwp->w_doto; 46 | curbp->b_markp = curwp->w_markp; 47 | curbp->b_marko = curwp->w_marko; 48 | } 49 | curbp = bp; /* Switch. */ 50 | if (curbp->b_active != TRUE) 51 | { /* buffer not active yet */ 52 | /* read it in and activate it */ 53 | readin (curbp->b_fname); 54 | curbp->b_dotp = lforw (curbp->b_linep); 55 | curbp->b_doto = 0; 56 | curbp->b_active = TRUE; 57 | } 58 | curwp->w_bufp = bp; 59 | curwp->w_linep = bp->b_linep; /* For macros, ignored */ 60 | curwp->w_flag |= WFMODE | WFFORCE | WFHARD; /* Quite nasty */ 61 | if (bp->b_nwnd++ == 0) 62 | { /* First use */ 63 | curwp->w_dotp = bp->b_dotp; 64 | curwp->w_doto = bp->b_doto; 65 | curwp->w_markp = bp->b_markp; 66 | curwp->w_marko = bp->b_marko; 67 | return (TRUE); 68 | } 69 | wp = wheadp; /* Look for old */ 70 | while (wp != 0) 71 | { 72 | if (wp != curwp && wp->w_bufp == bp) 73 | { 74 | curwp->w_dotp = wp->w_dotp; 75 | curwp->w_doto = wp->w_doto; 76 | curwp->w_markp = wp->w_markp; 77 | curwp->w_marko = wp->w_marko; 78 | break; 79 | } 80 | wp = wp->w_wndp; 81 | } 82 | return (TRUE); 83 | } 84 | 85 | /* 86 | * Attach a buffer to a window. The values of dot and mark come from the buffer 87 | * if the use count is 0. Otherwise, they come from some other window. 88 | */ 89 | /* ARGSUSED0 */ 90 | int usebuffer (int f, int n) 91 | { 92 | BUFFER *bp; 93 | char bufn[NBUFN]; 94 | int s; 95 | 96 | if ((s = mlreply ("Use buffer: ", bufn, NBUFN)) != TRUE) 97 | return (s); 98 | if ((bp = bfind (bufn, TRUE, 0)) == NULL) 99 | return (FALSE); 100 | return (swbuffer (bp)); 101 | } 102 | 103 | /* switch to the next buffer in the buffer list 104 | */ 105 | /* ARGSUSED0 */ 106 | int nextbuffer (int f, int n) 107 | { 108 | BUFFER *bp; 109 | 110 | bp = curbp->b_bufp; 111 | /* cycle through the buffers to find an eligable one */ 112 | while ((bp == NULL) || (bp->b_flag & BFTEMP)) 113 | { 114 | if (bp == NULL) 115 | bp = bheadp; 116 | else 117 | bp = bp->b_bufp; 118 | } 119 | return (swbuffer (bp)); 120 | } 121 | 122 | /* 123 | * Dispose of a buffer, by name. Ask for the name. Look it up (don't get too 124 | * upset if it isn't there at all!). Get quite upset if the buffer is being 125 | * displayed. Clear the buffer (ask if the buffer has been changed). Then free 126 | * the header line and the buffer header. Bound to "C-X K". 127 | */ 128 | /* ARGSUSED0 */ 129 | int killbuffer (int f, int n) 130 | { 131 | BUFFER *bp; 132 | char bufn[NBUFN]; 133 | int s; 134 | 135 | if ((s = mlreply ("Kill buffer: ", bufn, NBUFN)) != TRUE) 136 | return (s); 137 | if ((bp = bfind (bufn, FALSE, 0)) == NULL) /* Easy if unknown */ 138 | return (TRUE); 139 | return (zotbuf (bp)); 140 | } 141 | 142 | /* kill the buffer pointed to by bp 143 | */ 144 | int zotbuf (BUFFER *bp) 145 | { 146 | BUFFER *bp1, *bp2; 147 | int s; 148 | 149 | if (bp->b_nwnd != 0) 150 | { /* Error if on screen */ 151 | mlwrite ("Buffer is being displayed"); 152 | return (FALSE); 153 | } 154 | if ((s = bclear (bp)) != TRUE) /* Blow text away */ 155 | return (s); 156 | free (bp->b_linep); /* Release header line */ 157 | bp1 = 0; /* Find the header */ 158 | bp2 = bheadp; 159 | while (bp2 != bp) 160 | { 161 | bp1 = bp2; 162 | bp2 = bp2->b_bufp; 163 | } 164 | bp2 = bp2->b_bufp; /* Next one in chain */ 165 | if (bp1 == NULL) /* Unlink it */ 166 | bheadp = bp2; 167 | else 168 | bp1->b_bufp = bp2; 169 | free (bp); /* Release buffer block */ 170 | return (TRUE); 171 | } 172 | 173 | /* Rename the current buffer 174 | */ 175 | /* ARGSUSED0 */ 176 | int namebuffer (int f, int n) 177 | { 178 | BUFFER *bp; /* pointer to scan through all buffers */ 179 | char bufn[NBUFN]; /* buffer to hold buffer name */ 180 | 181 | /* prompt for and get the new buffer name */ 182 | ask: 183 | if (mlreply ("Change buffer name to: ", bufn, NBUFN) != TRUE) 184 | return (FALSE); 185 | 186 | /* and check for duplicates */ 187 | bp = bheadp; 188 | while (bp != 0) 189 | { 190 | if (bp != curbp) 191 | { 192 | /* if the names the same */ 193 | if (strcmp (bufn, bp->b_bname) == 0) 194 | goto ask; /* try again */ 195 | } 196 | bp = bp->b_bufp; /* onward */ 197 | } 198 | 199 | strncpy (curbp->b_bname, bufn, NBUFN); /* copy buffer name to structure */ 200 | curwp->w_flag |= WFMODE; /* make mode line replot */ 201 | mlerase (); 202 | return (TRUE); 203 | } 204 | 205 | /* 206 | * List all of the active buffers. First update the special buffer that holds 207 | * the list. Next make sure at least 1 window is displaying the buffer list, 208 | * splitting the screen if this is what it takes. Lastly, repaint all of the 209 | * windows that are displaying the list. Bound to "C-X C-B". 210 | */ 211 | /* ARGSUSED0 */ 212 | int listbuffers (int f, int n) 213 | { 214 | WINDOW *wp; 215 | BUFFER *bp; 216 | int s; 217 | 218 | if ((s = makelist ()) != TRUE) 219 | return (s); 220 | if (blistp->b_nwnd == 0) 221 | { /* Not on screen yet */ 222 | if ((wp = wpopup ()) == NULL) 223 | return (FALSE); 224 | bp = wp->w_bufp; 225 | if (--bp->b_nwnd == 0) 226 | { 227 | bp->b_dotp = wp->w_dotp; 228 | bp->b_doto = wp->w_doto; 229 | bp->b_markp = wp->w_markp; 230 | bp->b_marko = wp->w_marko; 231 | } 232 | wp->w_bufp = blistp; 233 | ++blistp->b_nwnd; 234 | } 235 | wp = wheadp; 236 | while (wp != 0) 237 | { 238 | if (wp->w_bufp == blistp) 239 | { 240 | wp->w_linep = lforw (blistp->b_linep); 241 | wp->w_dotp = lforw (blistp->b_linep); 242 | wp->w_doto = 0; 243 | wp->w_markp = 0; 244 | wp->w_marko = 0; 245 | wp->w_flag |= WFMODE | WFHARD; 246 | } 247 | wp = wp->w_wndp; 248 | } 249 | return (TRUE); 250 | } 251 | 252 | /* 253 | * This routine rebuilds the text in the special secret buffer that holds the 254 | * buffer list. It is called by the list buffers command. Return TRUE if 255 | * everything works. Return FALSE if there is an error (if there is no 256 | * memory). 257 | */ 258 | int makelist () 259 | { 260 | BUFFER *bp; 261 | LINE *lp; 262 | char *cp1, *cp2; 263 | char b[7], line[128]; 264 | int nbytes, s, c; 265 | 266 | blistp->b_flag &= ~BFCHG; /* Don't complain! */ 267 | if ((s = bclear (blistp)) != TRUE) /* Blow old text away */ 268 | return (s); 269 | strncpy (blistp->b_fname, "", 1); 270 | if (addline ("AC Size Buffer File") == FALSE || 271 | addline ("-- ------- ------ ----") == FALSE) 272 | return (FALSE); 273 | bp = bheadp; 274 | 275 | /* build line to report global mode settings */ 276 | cp1 = &line[0]; 277 | *cp1++ = ' '; 278 | *cp1++ = ' '; 279 | *cp1++ = ' '; 280 | 281 | /* output the list of buffers */ 282 | while (bp != 0) 283 | { 284 | if ((bp->b_flag & BFTEMP) != 0) 285 | { /* Skip magic ones */ 286 | bp = bp->b_bufp; 287 | continue; 288 | } 289 | cp1 = &line[0]; /* Start at left edge */ 290 | 291 | /* output status of ACTIVE flag (has the file been read in? */ 292 | if (bp->b_active == TRUE) /* "@" if activated */ 293 | *cp1++ = '@'; 294 | else 295 | *cp1++ = ' '; 296 | 297 | /* output status of changed flag */ 298 | if ((bp->b_flag & BFCHG) != 0) /* "*" if changed */ 299 | *cp1++ = '*'; 300 | else 301 | *cp1++ = ' '; 302 | *cp1++ = ' '; /* Gap */ 303 | 304 | nbytes = 0; /* Count bytes in buf */ 305 | lp = lforw (bp->b_linep); 306 | while (lp != bp->b_linep) 307 | { 308 | nbytes += llength (lp) + 1; 309 | lp = lforw (lp); 310 | } 311 | itoa (b, 6, nbytes); /* 6 digit buffer size */ 312 | cp2 = &b[0]; 313 | while ((c = *cp2++) != 0) 314 | *cp1++ = (char)c; 315 | *cp1++ = ' '; /* Gap */ 316 | cp2 = &bp->b_bname[0]; /* Buffer name */ 317 | while ((c = *cp2++) != 0) 318 | *cp1++ = (char)c; 319 | cp2 = &bp->b_fname[0]; /* File name */ 320 | if (*cp2 != 0) 321 | { 322 | while (cp1 < &line[2 + 1 + 5 + 1 + 6 + 1 + NBUFN]) /* XXX ??? */ 323 | *cp1++ = ' '; 324 | while ((c = *cp2++) != 0) 325 | { 326 | if (cp1 < &line[128 - 1]) 327 | *cp1++ = (char)c; 328 | } 329 | } 330 | *cp1 = 0; /* Add to the buffer */ 331 | if (addline (line) == FALSE) 332 | return (FALSE); 333 | bp = bp->b_bufp; 334 | } 335 | return (TRUE); /* All done */ 336 | } 337 | 338 | void itoa (char buf[], int width, int num) 339 | { 340 | buf[width] = 0; /* End of string */ 341 | while (num >= 10) 342 | { /* Conditional digits */ 343 | buf[--width] = (char)((num % 10) + '0'); 344 | num /= 10; 345 | } 346 | buf[--width] = (char)(num + '0'); /* Always 1 digit */ 347 | while (width != 0) /* Pad with blanks */ 348 | buf[--width] = ' '; 349 | } 350 | 351 | /* 352 | * The argument "text" points to a string. Append this line to the buffer list 353 | * buffer. Handcraft the EOL on the end. Return TRUE if it worked and FALSE if 354 | * you ran out of room. 355 | */ 356 | int addline (char *text) 357 | { 358 | LINE *lp; 359 | int ntext, i; 360 | 361 | ntext = strlen (text); 362 | if ((lp = lalloc (ntext)) == NULL) 363 | return (FALSE); 364 | for (i = 0; i < ntext; ++i) 365 | lputc (lp, i, text[i]); 366 | blistp->b_linep->l_bp->l_fp = lp; /* Hook onto the end */ 367 | lp->l_bp = blistp->b_linep->l_bp; 368 | blistp->b_linep->l_bp = lp; 369 | lp->l_fp = blistp->b_linep; 370 | if (blistp->b_dotp == blistp->b_linep) /* If "." is at the end */ 371 | blistp->b_dotp = lp; /* move it to new line */ 372 | return (TRUE); 373 | } 374 | 375 | /* 376 | * Look through the list of buffers. Return TRUE if there are any changed 377 | * buffers. Buffers that hold magic internal stuff are not considered; who 378 | * cares if the list of buffer names is hacked. Return FALSE if no buffers 379 | * have been changed. 380 | */ 381 | int anycb () 382 | { 383 | BUFFER *bp; 384 | 385 | bp = bheadp; 386 | while (bp != NULL) 387 | { 388 | if ((bp->b_flag & BFTEMP) == 0 && (bp->b_flag & BFCHG) != 0) 389 | return (TRUE); 390 | bp = bp->b_bufp; 391 | } 392 | return (FALSE); 393 | } 394 | 395 | /* 396 | * Find a buffer, by name. Return a pointer to the BUFFER structure associated 397 | * with it. If the named buffer is found, but is a TEMP buffer (like the 398 | * buffer list) conplain. If the buffer is not found and the "cflag" is TRUE, 399 | * create it. The "bflag" is the settings for the flags in in buffer. 400 | */ 401 | BUFFER* bfind (char *bname, int cflag, int bflag) 402 | { 403 | BUFFER *bp, *sb; 404 | LINE *lp; 405 | 406 | bp = bheadp; 407 | while (bp != 0) 408 | { 409 | if (strcmp (bname, bp->b_bname) == 0) 410 | { 411 | if ((bp->b_flag & BFTEMP) != 0) 412 | { 413 | mlwrite ("Cannot select builtin buffer"); 414 | return (0); 415 | } 416 | return (bp); 417 | } 418 | bp = bp->b_bufp; 419 | } 420 | if (cflag != FALSE) 421 | { 422 | if ((bp = (BUFFER *) malloc (sizeof (BUFFER))) == NULL) 423 | return (0); 424 | if ((lp = lalloc (0)) == NULL) 425 | { 426 | free (bp); 427 | return (BUFFER*)0; 428 | } 429 | /* find the place in the list to insert this buffer */ 430 | if (bheadp == NULL || strcmp (bheadp->b_bname, bname) > 0) 431 | { 432 | /* insert at the begining */ 433 | bp->b_bufp = bheadp; 434 | bheadp = bp; 435 | } 436 | else 437 | { 438 | sb = bheadp; 439 | while (sb->b_bufp != 0) 440 | { 441 | if (strcmp (sb->b_bufp->b_bname, bname) > 0) 442 | break; 443 | sb = sb->b_bufp; 444 | } 445 | 446 | /* and insert it */ 447 | bp->b_bufp = sb->b_bufp; 448 | sb->b_bufp = bp; 449 | } 450 | 451 | /* and set up the other buffer fields */ 452 | bp->b_active = TRUE; 453 | bp->b_dotp = lp; 454 | bp->b_doto = 0; 455 | bp->b_markp = 0; 456 | bp->b_marko = 0; 457 | bp->b_flag = (char)bflag; 458 | bp->b_nwnd = 0; 459 | bp->b_linep = lp; 460 | strncpy (bp->b_fname, "", 1); 461 | strncpy (bp->b_bname, bname, NBUFN); 462 | lp->l_fp = lp; 463 | lp->l_bp = lp; 464 | } 465 | return (bp); 466 | } 467 | 468 | /* 469 | * This routine blows away all of the text in a buffer. If the buffer is 470 | * marked as changed then we ask if it is ok to blow it away; this is to save 471 | * the user the grief of losing text. The window chain is nearly always wrong 472 | * if this gets called; the caller must arrange for the updates that are 473 | * required. Return TRUE if everything looks good. 474 | */ 475 | int bclear (BUFFER *bp) 476 | { 477 | LINE *lp; 478 | int s; 479 | 480 | if ((bp->b_flag & BFTEMP) == 0 /* Not scratch buffer */ 481 | && (bp->b_flag & BFCHG) != 0 /* Something changed */ 482 | && (s = mlyesno ("Discard changes")) != TRUE) 483 | return (s); 484 | bp->b_flag &= ~BFCHG; /* Not changed */ 485 | while ((lp = lforw (bp->b_linep)) != bp->b_linep) 486 | lfree (lp); 487 | bp->b_dotp = bp->b_linep; /* Fix "." */ 488 | bp->b_doto = 0; 489 | bp->b_markp = 0; /* Invalidate "mark" */ 490 | bp->b_marko = 0; 491 | return (TRUE); 492 | } 493 | -------------------------------------------------------------------------------- /line.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The functions in this file are a general set of line management utilities. 3 | * They are the only routines that touch the text. They also touch the buffer 4 | * and window structures, to make sure that the necessary updating gets done. 5 | * There are routines in this file that handle the kill buffer too. It isn't 6 | * here for any good reason. 7 | * 8 | * Note that this code only updates the dot and mark values in the window 9 | * list. Since all the code acts on the current window, the buffer that we are 10 | * editing must be being displayed, which means that "b_nwnd" is non zero, 11 | * which means that the dot and mark values in the buffer headers are 12 | * nonsense 13 | */ 14 | 15 | #include /* malloc(3) */ 16 | #include "estruct.h" 17 | #include "edef.h" 18 | 19 | extern void mlwrite (); 20 | extern int backchar (int f, int n); 21 | 22 | LINE* lalloc (int used); 23 | void lfree (LINE *lp); 24 | void lchange (int flag); 25 | int linsert (int n, int c); 26 | int lnewline (); 27 | int ldelete (int n, int kflag); 28 | int ldelnewline (); 29 | void kdelete (); 30 | int kinsert (int c); 31 | int kremove (int n); 32 | 33 | #define NBLOCK 16 /* Line block chunk size */ 34 | #define KBLOCK 1024 /* Kill buffer block size */ 35 | 36 | char *kbufp = NULL; /* Kill buffer data */ 37 | unsigned long kused = 0; /* # of bytes used in KB */ 38 | unsigned long ksize = 0; /* # of bytes allocated in KB */ 39 | 40 | /* 41 | * This routine allocates a block of memory large enough to hold a LINE 42 | * containing "used" characters. The block is always rounded up a bit. Return 43 | * a pointer to the new block, or NULL if there isn't any memory left. Print a 44 | * message in the message line if no space. 45 | */ 46 | LINE* lalloc (int used) 47 | { 48 | LINE *lp; 49 | int size; 50 | //char *malloc (); 51 | 52 | size = (used + NBLOCK - 1) & ~(NBLOCK - 1); 53 | if (size == 0) /* Assume that an empty */ 54 | size = NBLOCK; /* line is for type-in */ 55 | if ((lp = (LINE *) malloc (sizeof (LINE) + size)) == NULL) 56 | { 57 | mlwrite ("Cannot allocate %d bytes", size); 58 | return (NULL); 59 | } 60 | lp->l_size = size; 61 | lp->l_used = used; 62 | return (lp); 63 | } 64 | 65 | /* 66 | * Delete line "lp". Fix all of the links that might point at it (they are 67 | * moved to offset 0 of the next line. Unlink the line from whatever buffer it 68 | * might be in. Release the memory. The buffers are updated too; the magic 69 | * conditions described in the above comments don't hold here 70 | */ 71 | void lfree (LINE *lp) 72 | { 73 | BUFFER *bp; 74 | WINDOW *wp; 75 | 76 | wp = wheadp; 77 | while (wp != NULL) 78 | { 79 | if (wp->w_linep == lp) 80 | wp->w_linep = lp->l_fp; 81 | if (wp->w_dotp == lp) 82 | { 83 | wp->w_dotp = lp->l_fp; 84 | wp->w_doto = 0; 85 | } 86 | if (wp->w_markp == lp) 87 | { 88 | wp->w_markp = lp->l_fp; 89 | wp->w_marko = 0; 90 | } 91 | wp = wp->w_wndp; 92 | } 93 | bp = bheadp; 94 | while (bp != NULL) 95 | { 96 | if (bp->b_nwnd == 0) 97 | { 98 | if (bp->b_dotp == lp) 99 | { 100 | bp->b_dotp = lp->l_fp; 101 | bp->b_doto = 0; 102 | } 103 | if (bp->b_markp == lp) 104 | { 105 | bp->b_markp = lp->l_fp; 106 | bp->b_marko = 0; 107 | } 108 | } 109 | bp = bp->b_bufp; 110 | } 111 | lp->l_bp->l_fp = lp->l_fp; 112 | lp->l_fp->l_bp = lp->l_bp; 113 | free ((char *) lp); 114 | } 115 | 116 | /* 117 | * This routine gets called when a character is changed in place in the 118 | * current buffer. It updates all of the required flags in the buffer and 119 | * window system. The flag used is passed as an argument; if the buffer is 120 | * being displayed in more than 1 window we change EDIT t HARD. Set MODE if 121 | * the mode line needs to be updated (the "*" has to be set). 122 | */ 123 | void lchange (int flag) 124 | { 125 | WINDOW *wp; 126 | 127 | if (curbp->b_nwnd != 1) /* Ensure hard */ 128 | flag = WFHARD; 129 | if ((curbp->b_flag & BFCHG) == 0) 130 | { /* First change, so */ 131 | flag |= WFMODE; /* update mode lines */ 132 | curbp->b_flag |= BFCHG; 133 | } 134 | wp = wheadp; 135 | while (wp != NULL) 136 | { 137 | if (wp->w_bufp == curbp) 138 | wp->w_flag |= flag; 139 | wp = wp->w_wndp; 140 | } 141 | } 142 | 143 | /* 144 | * Insert "n" copies of the character "c" at the current location of dot. In 145 | * the easy case all that happens is the text is stored in the line. In the 146 | * hard case, the line has to be reallocated. When the window list is updated, 147 | * take special care; I screwed it up once. You always update dot in the 148 | * current window. You update mark, and a dot in another window, if it is 149 | * greater than the place where you did the insert. Return TRUE if all is 150 | * well, and FALSE on errors 151 | */ 152 | int linsert (int n, int c) 153 | { 154 | WINDOW *wp; 155 | LINE *lp1, *lp2, *lp3; 156 | char *cp1, *cp2; 157 | int i, doto; 158 | 159 | lchange (WFEDIT); 160 | lp1 = curwp->w_dotp; /* Current line */ 161 | if (lp1 == curbp->b_linep) 162 | { /* At the end: special */ 163 | if (curwp->w_doto != 0) 164 | { 165 | mlwrite ("bug: linsert"); 166 | return (FALSE); 167 | } 168 | if ((lp2 = lalloc (n)) == NULL) /* Allocate new line */ 169 | return (FALSE); 170 | lp3 = lp1->l_bp; /* Previous line */ 171 | lp3->l_fp = lp2; /* Link in */ 172 | lp2->l_fp = lp1; 173 | lp1->l_bp = lp2; 174 | lp2->l_bp = lp3; 175 | for (i = 0; i < n; ++i) 176 | lp2->l_text[i] = c; 177 | curwp->w_dotp = lp2; 178 | curwp->w_doto = n; 179 | return (TRUE); 180 | } 181 | doto = curwp->w_doto; /* Save for later */ 182 | if (lp1->l_used + n > lp1->l_size) 183 | { /* Hard: reallocate */ 184 | if ((lp2 = lalloc (lp1->l_used + n)) == NULL) 185 | return (FALSE); 186 | cp1 = &lp1->l_text[0]; 187 | cp2 = &lp2->l_text[0]; 188 | while (cp1 != &lp1->l_text[doto]) 189 | *cp2++ = *cp1++; 190 | cp2 += n; 191 | while (cp1 != &lp1->l_text[lp1->l_used]) 192 | *cp2++ = *cp1++; 193 | lp1->l_bp->l_fp = lp2; 194 | lp2->l_fp = lp1->l_fp; 195 | lp1->l_fp->l_bp = lp2; 196 | lp2->l_bp = lp1->l_bp; 197 | free ((char *) lp1); 198 | } 199 | else 200 | { /* Easy: in place */ 201 | lp2 = lp1; /* Pretend new line */ 202 | lp2->l_used += n; 203 | cp2 = &lp1->l_text[lp1->l_used]; 204 | cp1 = cp2 - n; 205 | while (cp1 != &lp1->l_text[doto]) 206 | *--cp2 = *--cp1; 207 | } 208 | for (i = 0; i < n; ++i) /* Add the characters */ 209 | lp2->l_text[doto + i] = c; 210 | wp = wheadp; /* Update windows */ 211 | while (wp != NULL) 212 | { 213 | if (wp->w_linep == lp1) 214 | wp->w_linep = lp2; 215 | if (wp->w_dotp == lp1) 216 | { 217 | wp->w_dotp = lp2; 218 | if (wp == curwp || wp->w_doto > doto) 219 | wp->w_doto += n; 220 | } 221 | if (wp->w_markp == lp1) 222 | { 223 | wp->w_markp = lp2; 224 | if (wp->w_marko > doto) 225 | wp->w_marko += n; 226 | } 227 | wp = wp->w_wndp; 228 | } 229 | return (TRUE); 230 | } 231 | 232 | /* 233 | * Insert a newline into the buffer at the current location of dot in the 234 | * current window. The funny ass-backwards way it does things is not a botch; 235 | * it just makes the last line in the file not a special case. Return TRUE if 236 | * everything works out and FALSE on error (memory allocation failure). The 237 | * update of dot and mark is a bit easier then in the above case, because the 238 | * split forces more updating. 239 | */ 240 | int lnewline () 241 | { 242 | WINDOW *wp; 243 | char *cp1, *cp2; 244 | LINE *lp1, *lp2; 245 | int doto; 246 | 247 | lchange (WFHARD); 248 | lp1 = curwp->w_dotp; /* Get the address and */ 249 | doto = curwp->w_doto; /* offset of "." */ 250 | if ((lp2 = lalloc (doto)) == NULL) /* New first half line */ 251 | return (FALSE); 252 | cp1 = &lp1->l_text[0]; /* Shuffle text around */ 253 | cp2 = &lp2->l_text[0]; 254 | while (cp1 != &lp1->l_text[doto]) 255 | *cp2++ = *cp1++; 256 | cp2 = &lp1->l_text[0]; 257 | while (cp1 != &lp1->l_text[lp1->l_used]) 258 | *cp2++ = *cp1++; 259 | lp1->l_used -= doto; 260 | lp2->l_bp = lp1->l_bp; 261 | lp1->l_bp = lp2; 262 | lp2->l_bp->l_fp = lp2; 263 | lp2->l_fp = lp1; 264 | wp = wheadp; /* Windows */ 265 | while (wp != NULL) 266 | { 267 | if (wp->w_linep == lp1) 268 | wp->w_linep = lp2; 269 | if (wp->w_dotp == lp1) 270 | { 271 | if (wp->w_doto < doto) 272 | wp->w_dotp = lp2; 273 | else 274 | wp->w_doto -= doto; 275 | } 276 | if (wp->w_markp == lp1) 277 | { 278 | if (wp->w_marko < doto) 279 | wp->w_markp = lp2; 280 | else 281 | wp->w_marko -= doto; 282 | } 283 | wp = wp->w_wndp; 284 | } 285 | return (TRUE); 286 | } 287 | 288 | /* 289 | * This function deletes "n" bytes, starting at dot. It understands how do 290 | * deal with end of lines, etc. It returns TRUE if all of the characters were 291 | * deleted, and FALSE if they were not (because dot ran into the end of the 292 | * buffer. The "kflag" is TRUE if the text should be put in the kill buffer. 293 | */ 294 | int ldelete (int n, int kflag) 295 | { 296 | LINE *dotp; 297 | WINDOW *wp; 298 | char *cp1, *cp2; 299 | int doto, chunk; 300 | 301 | while (n != 0) 302 | { 303 | dotp = curwp->w_dotp; 304 | doto = curwp->w_doto; 305 | if (dotp == curbp->b_linep) /* Hit end of buffer */ 306 | return (FALSE); 307 | chunk = dotp->l_used - doto; /* Size of chunk */ 308 | if (chunk > n) 309 | chunk = n; 310 | if (chunk == 0) 311 | { /* End of line, merge */ 312 | lchange (WFHARD); 313 | if (ldelnewline () == FALSE 314 | || (kflag != FALSE && kinsert ('\n') == FALSE)) 315 | return (FALSE); 316 | --n; 317 | continue; 318 | } 319 | lchange (WFEDIT); 320 | cp1 = &dotp->l_text[doto]; /* Scrunch text */ 321 | cp2 = cp1 + chunk; 322 | if (kflag != FALSE) 323 | { /* Kill? */ 324 | while (cp1 != cp2) 325 | { 326 | if (kinsert (*cp1) == FALSE) 327 | return (FALSE); 328 | ++cp1; 329 | } 330 | cp1 = &dotp->l_text[doto]; 331 | } 332 | while (cp2 != &dotp->l_text[dotp->l_used]) 333 | *cp1++ = *cp2++; 334 | dotp->l_used -= chunk; 335 | wp = wheadp; /* Fix windows */ 336 | while (wp != NULL) 337 | { 338 | if (wp->w_dotp == dotp && wp->w_doto >= doto) 339 | { 340 | wp->w_doto -= chunk; 341 | if (wp->w_doto < doto) 342 | wp->w_doto = doto; 343 | } 344 | if (wp->w_markp == dotp && wp->w_marko >= doto) 345 | { 346 | wp->w_marko -= chunk; 347 | if (wp->w_marko < doto) 348 | wp->w_marko = doto; 349 | } 350 | wp = wp->w_wndp; 351 | } 352 | n -= chunk; 353 | } 354 | return (TRUE); 355 | } 356 | 357 | /* 358 | * Delete a newline. Join the current line with the next line. If the next 359 | * line is the magic header line always return TRUE; merging the last line 360 | * with the header line can be thought of as always being a successful 361 | * operation, even if nothing is done, and this makes the kill buffer work 362 | * "right". Easy cases can be done by shuffling data around. Hard cases 363 | * require that lines be moved about in memory. Return FALSE on error and TRUE 364 | * if all looks ok. Called by "ldelete" only. 365 | */ 366 | int ldelnewline () 367 | { 368 | LINE *lp1, *lp2, *lp3; 369 | WINDOW *wp; 370 | char *cp1, *cp2; 371 | 372 | lp1 = curwp->w_dotp; 373 | lp2 = lp1->l_fp; 374 | if (lp2 == curbp->b_linep) 375 | { /* At the buffer end */ 376 | if (lp1->l_used == 0) /* Blank line */ 377 | lfree (lp1); 378 | return (TRUE); 379 | } 380 | if (lp2->l_used <= lp1->l_size - lp1->l_used) 381 | { 382 | cp1 = &lp1->l_text[lp1->l_used]; 383 | cp2 = &lp2->l_text[0]; 384 | while (cp2 != &lp2->l_text[lp2->l_used]) 385 | *cp1++ = *cp2++; 386 | wp = wheadp; 387 | while (wp != NULL) 388 | { 389 | if (wp->w_linep == lp2) 390 | wp->w_linep = lp1; 391 | if (wp->w_dotp == lp2) 392 | { 393 | wp->w_dotp = lp1; 394 | wp->w_doto += lp1->l_used; 395 | } 396 | if (wp->w_markp == lp2) 397 | { 398 | wp->w_markp = lp1; 399 | wp->w_marko += lp1->l_used; 400 | } 401 | wp = wp->w_wndp; 402 | } 403 | lp1->l_used += lp2->l_used; 404 | lp1->l_fp = lp2->l_fp; 405 | lp2->l_fp->l_bp = lp1; 406 | free ((char *) lp2); 407 | return (TRUE); 408 | } 409 | if ((lp3 = lalloc (lp1->l_used + lp2->l_used)) == NULL) 410 | return (FALSE); 411 | cp1 = &lp1->l_text[0]; 412 | cp2 = &lp3->l_text[0]; 413 | while (cp1 != &lp1->l_text[lp1->l_used]) 414 | *cp2++ = *cp1++; 415 | cp1 = &lp2->l_text[0]; 416 | while (cp1 != &lp2->l_text[lp2->l_used]) 417 | *cp2++ = *cp1++; 418 | lp1->l_bp->l_fp = lp3; 419 | lp3->l_fp = lp2->l_fp; 420 | lp2->l_fp->l_bp = lp3; 421 | lp3->l_bp = lp1->l_bp; 422 | wp = wheadp; 423 | while (wp != NULL) 424 | { 425 | if (wp->w_linep == lp1 || wp->w_linep == lp2) 426 | wp->w_linep = lp3; 427 | if (wp->w_dotp == lp1) 428 | wp->w_dotp = lp3; 429 | else if (wp->w_dotp == lp2) 430 | { 431 | wp->w_dotp = lp3; 432 | wp->w_doto += lp1->l_used; 433 | } 434 | if (wp->w_markp == lp1) 435 | wp->w_markp = lp3; 436 | else if (wp->w_markp == lp2) 437 | { 438 | wp->w_markp = lp3; 439 | wp->w_marko += lp1->l_used; 440 | } 441 | wp = wp->w_wndp; 442 | } 443 | free ((char *) lp1); 444 | free ((char *) lp2); 445 | return (TRUE); 446 | } 447 | 448 | /* 449 | * Delete all of the text saved in the kill buffer. Called by commands when a 450 | * new kill context is being created. The kill buffer array is released, just 451 | * in case the buffer has grown to immense size. No errors. 452 | */ 453 | void kdelete () 454 | { 455 | if (kbufp != NULL) 456 | { 457 | free ((char *) kbufp); 458 | kbufp = NULL; 459 | kused = 0; 460 | ksize = 0; 461 | } 462 | } 463 | 464 | /* 465 | * Insert a character to the kill buffer, enlarging the buffer if there isn't 466 | * any room. Always grow the buffer in chunks, on the assumption that if you 467 | * put something in the kill buffer you are going to put more stuff there too 468 | * later. Return TRUE if all is well, and FALSE on errors. 469 | */ 470 | int kinsert (int c) 471 | { 472 | //char *realloc (); 473 | //char *malloc (); 474 | char *nbufp; 475 | 476 | if (kused == ksize) 477 | { 478 | if (ksize == 0) /* first time through? */ 479 | nbufp = malloc (KBLOCK); /* alloc the first block */ 480 | else /* or re allocate a bigger block */ 481 | nbufp = realloc (kbufp, ksize + KBLOCK); 482 | if (nbufp == NULL) /* abort if it fails */ 483 | return (FALSE); 484 | kbufp = nbufp; /* point our global at it */ 485 | ksize += KBLOCK; /* and adjust the size */ 486 | } 487 | kbufp[kused++] = c; 488 | return (TRUE); 489 | } 490 | 491 | /* 492 | * This function gets characters from the kill buffer. If the character index 493 | * "n" is off the end, it returns "-1". This lets the caller just scan along 494 | * until it gets a "-1" back. 495 | */ 496 | int kremove (int n) 497 | { 498 | if (n >= kused) 499 | return (-1); 500 | else 501 | return (kbufp[n] & 0xFF); 502 | } 503 | -------------------------------------------------------------------------------- /search.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The functions in this file implement commands that search in the forward 3 | * and backward directions. There are no special characters in the search 4 | * strings 5 | */ 6 | 7 | #include /* strncpy(3), strncat(3) */ 8 | #include "estruct.h" 9 | #include "edef.h" 10 | 11 | extern void mlwrite (); 12 | extern int mlreplyt (char *prompt, char *buf, int nbuf, char eolchar); 13 | extern void update (); 14 | extern int forwchar (int f, int n); 15 | extern int ldelete (int n, int kflag); 16 | extern int lnewline (); 17 | extern int linsert (int n, int c); 18 | 19 | int forwsearch (int f, int n); 20 | int forwhunt (int f, int n); 21 | int backsearch (int f, int n); 22 | int backhunt (int f, int n); 23 | int bsearch (int f, int n); 24 | int eq (int bc, int pc); 25 | int readpattern (char *prompt); 26 | int sreplace (int f, int n); 27 | int qreplace (int f, int n); 28 | int replaces (int kind, int f, int n); 29 | int forscan (char *patrn, int leavep); 30 | void expandp (char *srcstr, char *deststr, int maxlength); 31 | 32 | #define PTBEG 1 /* leave the point at the begining on search */ 33 | #define PTEND 2 /* leave the point at the end on search */ 34 | 35 | /* 36 | * Search forward. Get a search string from the user, and search, beginning at 37 | * ".", for the string. If found, reset the "." to be just after the match 38 | * string, and [perhaps] repaint the display. Bound to "C-S" 39 | */ 40 | int forwsearch (int f, int n) 41 | { 42 | int status; 43 | 44 | if (n == 0) /* resolve the repeat count */ 45 | n = 1; 46 | if (n < 1) /* search backwards */ 47 | return (backsearch (f, -n)); 48 | 49 | /* ask the user for the text of a pattern */ 50 | if ((status = readpattern ("Search")) != TRUE) 51 | return (status); 52 | 53 | /* search for the pattern */ 54 | while (n-- > 0) 55 | { 56 | if ((status = forscan (&pat[0], PTEND)) == FALSE) 57 | break; 58 | } 59 | 60 | /* and complain if not there */ 61 | if (status == FALSE) 62 | mlwrite ("Not found"); 63 | return (status); 64 | } 65 | 66 | int forwhunt (int f, int n) 67 | { 68 | int status; 69 | 70 | /* resolve the repeat count */ 71 | if (n == 0) 72 | n = 1; 73 | if (n < 1) /* search backwards */ 74 | return (backhunt (f, -n)); 75 | 76 | /* Make sure a pattern exists */ 77 | if (pat[0] == 0) 78 | { 79 | mlwrite ("No pattern set"); 80 | return (FALSE); 81 | } 82 | /* search for the pattern */ 83 | while (n-- > 0) 84 | { 85 | if ((status = forscan (&pat[0], PTEND)) == FALSE) 86 | break; 87 | } 88 | 89 | /* and complain if not there */ 90 | if (status == FALSE) 91 | mlwrite ("Not found"); 92 | return (status); 93 | } 94 | 95 | /* 96 | * Reverse search. Get a search string from the user, and search, starting at 97 | * "." and proceeding toward the front of the buffer. If found "." is left 98 | * pointing at the first character of the pattern [the last character that was 99 | * matched]. Bound to "C-R" 100 | */ 101 | int backsearch (int f, int n) 102 | { 103 | int s; 104 | 105 | if (n == 0) /* resolve null and negative arguments */ 106 | n = 1; 107 | if (n < 1) 108 | return (forwsearch (f, -n)); 109 | 110 | if ((s = readpattern("Reverse search")) != TRUE)/* get a pattern to search */ 111 | return (s); 112 | 113 | return bsearch (f, n); /* and go search for it */ 114 | } 115 | 116 | /* hunt backward for the last search string entered 117 | */ 118 | int backhunt (int f, int n) 119 | { 120 | if (n == 0) /* resolve null and negative arguments */ 121 | n = 1; 122 | if (n < 1) 123 | return (forwhunt (f, -n)); 124 | 125 | if (pat[0] == 0) /* Make sure a pattern exists */ 126 | { 127 | mlwrite ("No pattern set"); 128 | return (FALSE); 129 | } 130 | return bsearch (f, n); /* go search */ 131 | } 132 | 133 | int bsearch (int f, int n) 134 | { 135 | LINE *clp, *tlp; 136 | char *epp, *pp; 137 | int cbo, tbo, c; 138 | 139 | /* find a pointer to the end of the pattern */ 140 | for (epp = &pat[0]; epp[1] != 0; ++epp) 141 | ; 142 | 143 | /* make local copies of the starting location */ 144 | clp = curwp->w_dotp; 145 | cbo = curwp->w_doto; 146 | 147 | while (n-- > 0) 148 | { 149 | for (;;) 150 | { 151 | /* if we are at the begining of the line, wrap back around */ 152 | if (cbo == 0) 153 | { 154 | clp = lback (clp); 155 | 156 | if (clp == curbp->b_linep) 157 | { 158 | mlwrite ("Not found"); 159 | return (FALSE); 160 | } 161 | cbo = llength (clp) + 1; 162 | } 163 | /* fake the at the end of a line */ 164 | if (--cbo == llength (clp)) 165 | c = '\n'; 166 | else 167 | c = lgetc (clp, cbo); 168 | 169 | /* check for a match against the end of the pattern */ 170 | if (eq (c, *epp) != FALSE) 171 | { 172 | tlp = clp; 173 | tbo = cbo; 174 | pp = epp; 175 | /* scanning backwards through the rest of the pattern 176 | * looking for a match */ 177 | while (pp != &pat[0]) 178 | { 179 | /* wrap across a line break */ 180 | if (tbo == 0) 181 | { 182 | tlp = lback (tlp); 183 | if (tlp == curbp->b_linep) 184 | goto fail; 185 | 186 | tbo = llength (tlp) + 1; 187 | } 188 | /* fake the */ 189 | if (--tbo == llength (tlp)) 190 | c = '\n'; 191 | else 192 | c = lgetc (tlp, tbo); 193 | 194 | if (eq (c, *--pp) == FALSE) 195 | goto fail; 196 | } 197 | 198 | /* A Match! reset the current cursor */ 199 | curwp->w_dotp = tlp; 200 | curwp->w_doto = tbo; 201 | curwp->w_flag |= WFMOVE; 202 | goto next; 203 | } 204 | fail:; 205 | } 206 | next:; 207 | } 208 | return (TRUE); 209 | } 210 | 211 | /* 212 | * Compare two characters. The "bc" comes from the buffer. It has it's case 213 | * folded out. The "pc" is from the pattern 214 | */ 215 | int eq (int bc, int pc) 216 | { 217 | if (bc >= 'a' && bc <= 'z') 218 | bc -= 0x20; 219 | if (pc >= 'a' && pc <= 'z') 220 | pc -= 0x20; 221 | if (bc == pc) 222 | return (TRUE); 223 | return (FALSE); 224 | } 225 | 226 | /* 227 | * Read a pattern. Stash it in the external variable "pat". The "pat" is not 228 | * updated if the user types in an empty line. If the user typed an empty 229 | * line, and there is no old pattern, it is an error. Display the old pattern, 230 | * in the style of Jeff Lomicka. There is some do-it-yourself control 231 | * expansion. change to using to delemit the end-of-pattern to allow 232 | * s in the search string 233 | */ 234 | int readpattern (char *prompt) 235 | { 236 | char tpat[NPAT + 20]; 237 | int s; 238 | 239 | strncpy (tpat, prompt, NPAT-12); /* copy prompt to output string */ 240 | strncat (tpat, " [", 3); /* build new prompt string */ 241 | expandp (&pat[0], &tpat[strlen (tpat)], NPAT / 2); /* add old pattern */ 242 | strncat (tpat, "]: ", 9); 243 | 244 | s = mlreplyt (tpat, tpat, NPAT, 27); /* Read pattern */ 245 | 246 | if (s == TRUE) /* Specified */ 247 | strncpy (pat, tpat, NPAT); 248 | else if (s == FALSE && pat[0] != 0) /* CR, but old one */ 249 | s = TRUE; 250 | 251 | return (s); 252 | } 253 | 254 | /* 255 | * Search and replace (ESC-R) 256 | */ 257 | int sreplace (int f, int n) 258 | { 259 | return (replaces (FALSE, f, n)); 260 | } 261 | 262 | /* 263 | * search and replace with query (ESC-CTRL-R) 264 | */ 265 | int qreplace (int f, int n) 266 | { 267 | return (replaces (TRUE, f, n)); 268 | } 269 | 270 | /* 271 | * replaces: search for a string and replace it with another string. query 272 | * might be enabled (according to kind) 273 | */ 274 | int replaces (int kind, int f, int n) 275 | { 276 | LINE *origline; /* original "." position */ 277 | char tmpc; /* temporary character */ 278 | char c; /* input char for query */ 279 | char tpat[NPAT]; /* temporary to hold search pattern */ 280 | int i; /* loop index */ 281 | int s; /* success flag on pattern inputs */ 282 | int slength, rlength; /* length of search and replace strings */ 283 | int numsub; /* number of substitutions */ 284 | int nummatch; /* number of found matches */ 285 | int nlflag; /* last char of search string a ? */ 286 | int nlrepl; /* was a replace done on the last line? */ 287 | int origoff; /* and offset (for . query option) */ 288 | 289 | /* check for negative repititions */ 290 | if (f && n < 0) 291 | return (FALSE); 292 | 293 | /* ask the user for the text of a pattern */ 294 | if ((s = readpattern ((kind == FALSE ? "Replace" : "Query replace"))) != TRUE) 295 | return (s); 296 | strncpy (&tpat[0], &pat[0], NPAT); /* salt it away */ 297 | 298 | /* ask for the replacement string */ 299 | strncpy (&pat[0], &rpat[0], NPAT); /* set up default string */ 300 | if ((s = readpattern ("with")) == ABORT) 301 | return (s); 302 | 303 | /* move everything to the right place and length them */ 304 | strncpy (&rpat[0], &pat[0], NPAT); 305 | strncpy (&pat[0], &tpat[0], NPAT); 306 | slength = strlen (&pat[0]); 307 | rlength = strlen (&rpat[0]); 308 | 309 | /* set up flags so we can make sure not to do a recursive replace on the 310 | * last line */ 311 | nlflag = (pat[slength - 1] == '\n'); 312 | nlrepl = FALSE; 313 | 314 | /* build query replace question string */ 315 | strncpy (tpat, "Replace '", 10); 316 | expandp (&pat[0], &tpat[strlen (tpat)], NPAT / 3); 317 | strncat (tpat, "' with '", 9); 318 | expandp (&rpat[0], &tpat[strlen (tpat)], NPAT / 3); 319 | strncat (tpat, "'? ", 4); 320 | 321 | /* save original . position */ 322 | origline = curwp->w_dotp; 323 | origoff = curwp->w_doto; 324 | 325 | /* scan through the file */ 326 | numsub = 0; 327 | nummatch = 0; 328 | while ((f == FALSE || n > nummatch) && 329 | (nlflag == FALSE || nlrepl == FALSE)) 330 | { 331 | /* search for the pattern */ 332 | if (forscan (&pat[0], PTBEG) != TRUE) 333 | break; /* all done */ 334 | ++nummatch; /* increment # of matches */ 335 | 336 | /* check if we are on the last line */ 337 | nlrepl = (lforw (curwp->w_dotp) == curwp->w_bufp->b_linep); 338 | 339 | /* check for query */ 340 | if (kind) 341 | { 342 | /* get the query */ 343 | mlwrite (&tpat[0], &pat[0], &rpat[0]); 344 | qprompt: 345 | update (); /* show the proposed place to change */ 346 | c = (*term.t_getchar) (); /* and input */ 347 | mlwrite (""); /* and clear it */ 348 | 349 | /* and respond appropriately */ 350 | switch (c) 351 | { 352 | case 'y': /* yes, substitute */ 353 | case ' ': 354 | break; 355 | 356 | case 'n': /* no, onword */ 357 | forwchar (FALSE, 1); 358 | continue; 359 | 360 | case '!': /* yes/stop asking */ 361 | kind = FALSE; 362 | break; 363 | 364 | case '.': /* abort! and return */ 365 | /* restore old position */ 366 | curwp->w_dotp = origline; 367 | curwp->w_doto = origoff; 368 | curwp->w_flag |= WFMOVE; 369 | 370 | case BELL: /* abort! and stay */ 371 | mlwrite ("Aborted!"); 372 | return (FALSE); 373 | 374 | case 0x0d: /* controlled exit */ 375 | case 'q': 376 | return (TRUE); 377 | 378 | default: /* bitch and beep */ 379 | (*term.t_beep) (); 380 | 381 | case '?': /* help me */ 382 | mlwrite ("(Y)es, (N)o, (!)Do the rest, (^G,RET,q)Abort, (.)Abort back, (?)Help: "); 383 | goto qprompt; 384 | } 385 | } 386 | /* delete the sucker */ 387 | if (ldelete (slength, FALSE) != TRUE) 388 | { 389 | /* error while deleting */ 390 | mlwrite ("ERROR while deleteing"); 391 | return (FALSE); 392 | } 393 | /* and insert its replacement */ 394 | for (i = 0; i < rlength; i++) 395 | { 396 | tmpc = rpat[i]; 397 | s = (tmpc == '\n' ? lnewline () : linsert (1, tmpc)); 398 | if (s != TRUE) 399 | { 400 | /* error while inserting */ 401 | mlwrite ("Out of memory while inserting"); 402 | return (FALSE); 403 | } 404 | } 405 | 406 | numsub++; /* increment # of substitutions */ 407 | } 408 | 409 | /* and report the results */ 410 | mlwrite ("%d substitutions", numsub); 411 | return (TRUE); 412 | } 413 | 414 | /* search forward for a 415 | */ 416 | int forscan (char *patrn, int leavep) 417 | { 418 | LINE *curline; /* current line during scan */ 419 | LINE *lastline; /* last line position during scan */ 420 | LINE *matchline; /* current line during matching */ 421 | char *patptr; /* pointer into pattern */ 422 | int curoff; /* position within current line */ 423 | int lastoff; /* position within last line */ 424 | int c; /* character at current position */ 425 | int matchoff; /* position in matching line */ 426 | 427 | /* setup local scan pointers to global "." */ 428 | curline = curwp->w_dotp; 429 | curoff = curwp->w_doto; 430 | 431 | /* scan each character until we hit the head link record */ 432 | while (curline != curbp->b_linep) 433 | { 434 | /* save the current position in case we need to restore it on a match */ 435 | lastline = curline; 436 | lastoff = curoff; 437 | 438 | /* get the current character resolving EOLs */ 439 | if (curoff == llength (curline)) 440 | { /* if at EOL */ 441 | curline = lforw (curline); /* skip to next line */ 442 | curoff = 0; 443 | c = '\n'; /* and return a */ 444 | } 445 | else 446 | c = lgetc (curline, curoff++); /* get the char */ 447 | 448 | /* test it against first char in pattern */ 449 | if (eq (c, patrn[0]) != FALSE) /* if we find it. */ 450 | { 451 | /* setup match pointers */ 452 | matchline = curline; 453 | matchoff = curoff; 454 | patptr = &patrn[0]; 455 | 456 | /* scan through patrn for a match */ 457 | while (*++patptr != 0) 458 | { 459 | /* advance all the pointers */ 460 | if (matchoff == llength (matchline)) 461 | { 462 | /* advance past EOL */ 463 | matchline = lforw (matchline); 464 | matchoff = 0; 465 | c = '\n'; 466 | } 467 | else 468 | c = lgetc (matchline, matchoff++); 469 | 470 | /* and test it against the pattern */ 471 | if (eq (*patptr, c) == FALSE) 472 | goto fail; 473 | } 474 | 475 | /* A SUCCESSFULL MATCH!!! */ 476 | /* reset the global "." pointers */ 477 | if (leavep == PTEND) 478 | { /* at end of string */ 479 | curwp->w_dotp = matchline; 480 | curwp->w_doto = matchoff; 481 | } 482 | else 483 | { /* at begining of string */ 484 | curwp->w_dotp = lastline; 485 | curwp->w_doto = lastoff; 486 | } 487 | curwp->w_flag |= WFMOVE; /* flag that we have moved */ 488 | return (TRUE); 489 | } 490 | fail:; /* continue to search */ 491 | } 492 | /* we could not find a match */ 493 | return (FALSE); 494 | } 495 | 496 | /* expandp: expand control key sequences for output 497 | */ 498 | void expandp (char *srcstr, char *deststr, int maxlength) 499 | { 500 | char c; /* current char to translate */ 501 | 502 | /* scan through the string */ 503 | while ((c = *srcstr++) != 0) 504 | { 505 | if (c == '\n') 506 | { /* its an EOL */ 507 | *deststr++ = '<'; 508 | *deststr++ = 'N'; 509 | *deststr++ = 'L'; 510 | *deststr++ = '>'; 511 | maxlength -= 4; 512 | } 513 | else if (c < 0x20 || c == 0x7f) 514 | { /* control character */ 515 | *deststr++ = '^'; 516 | *deststr++ = c ^ 0x40; 517 | maxlength -= 2; 518 | } 519 | else if (c == '%') 520 | { 521 | *deststr++ = '%'; 522 | *deststr++ = '%'; 523 | maxlength -= 2; 524 | } 525 | else 526 | { /* any other character */ 527 | *deststr++ = c; 528 | maxlength--; 529 | } 530 | 531 | /* check for maxlength */ 532 | if (maxlength < 4) 533 | { 534 | *deststr++ = '$'; 535 | *deststr = '\0'; 536 | return; 537 | } 538 | } 539 | *deststr = '\0'; 540 | return; 541 | } 542 | -------------------------------------------------------------------------------- /display.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The functions in this file handle redisplay. There are two halves, the ones 3 | * that update the virtual display screen, and the ones that make the physical 4 | * display screen the same as the virtual display screen. These functions use 5 | * hints that are left in the windows by the commands 6 | */ 7 | 8 | #include 9 | #include "estruct.h" 10 | #include "edef.h" 11 | 12 | extern int typahead (); 13 | extern int ctrlg (); 14 | 15 | void movecursor (int row, int col); 16 | void mlerase (); 17 | void vtinit (); 18 | void vttidy (); 19 | void vtmove (int row, int col); 20 | void vtputc (int c); 21 | void vtpute (int c); 22 | void vteeol (); 23 | void update (); 24 | void updext (); 25 | void updateline (int row, char vline[], char pline[], short *flags); 26 | void modeline (WINDOW *wp); 27 | void upmode (); 28 | int mlyesno (char *prompt); 29 | int mlreplyt (char *prompt, char *buf, int nbuf, char eolchar); 30 | int mlreply (char *prompt, char *buf, int nbuf); 31 | void mlwrite (); 32 | void mlputs (char *s); 33 | void mlputi (int i, int r); 34 | void mlputli (long l, int r); 35 | 36 | typedef struct VIDEO { 37 | short v_flag; /* Flags */ 38 | char v_text[1]; /* Screen data */ 39 | } VIDEO; 40 | 41 | #define VFCHG 0x0001 /* Changed flag */ 42 | #define VFEXT 0x0002 /* extended (beyond column 80) */ 43 | #define VFREV 0x0004 /* reverse video status */ 44 | #define VFREQ 0x0008 /* reverse video request */ 45 | 46 | int vtrow = 0; /* Row location of SW cursor */ 47 | int vtcol = 0; /* Column location of SW cursor */ 48 | int ttrow = HUGE; /* Row location of HW cursor */ 49 | int ttcol = HUGE; /* Column location of HW cursor */ 50 | int lbound = 0; /* leftmost column of line being displayed */ 51 | 52 | VIDEO **vscreen; /* Virtual screen */ 53 | VIDEO **pscreen; /* Physical screen */ 54 | 55 | /* 56 | * Send a command to the terminal to move the hardware cursor to row "row" and 57 | * column "col". The row and column arguments are origin 0. Optimize out 58 | * random calls. Update "ttrow" and "ttcol". 59 | */ 60 | void movecursor (int row, int col) 61 | { 62 | if (row != ttrow || col != ttcol) 63 | { 64 | ttrow = row; 65 | ttcol = col; 66 | (*term.t_move) (row, col); 67 | } 68 | } 69 | 70 | /* 71 | * Erase the message line. This is a special routine because the message line 72 | * is not considered to be part of the virtual screen. It always works 73 | * immediately; the terminal buffer is flushed via a call to the flusher. 74 | */ 75 | void mlerase () 76 | { 77 | int i; 78 | 79 | movecursor (term.t_nrow, 0); 80 | if (eolexist == TRUE) 81 | (*term.t_eeol) (); 82 | else 83 | { 84 | for (i = 0; i < term.t_ncol - 1; i++) 85 | (*term.t_putchar) (' '); 86 | movecursor (term.t_nrow, 1); /* force the move! */ 87 | movecursor (term.t_nrow, 0); 88 | } 89 | (*term.t_flush) (); 90 | mpresf = FALSE; 91 | } 92 | 93 | /* 94 | * Initialize the data structures used by the display code. The edge vectors 95 | * used to access the screens are set up. The operating system's terminal I/O 96 | * channel is set up. All the other things get initialized at compile time. 97 | * The original window has "WFCHG" set, so that it will get completely redrawn 98 | * on the first call to "update". 99 | */ 100 | void vtinit () 101 | { 102 | VIDEO *vp; 103 | //char *malloc (); 104 | int i; 105 | 106 | (*term.t_open) (); 107 | (*term.t_rev) (FALSE); 108 | vscreen = (VIDEO **) malloc (term.t_nrow * sizeof (VIDEO *)); 109 | 110 | if (vscreen == NULL) 111 | exit (1); 112 | 113 | pscreen = (VIDEO **) malloc (term.t_nrow * sizeof (VIDEO *)); 114 | 115 | if (pscreen == NULL) 116 | exit (1); 117 | 118 | for (i = 0; i < term.t_nrow; ++i) 119 | { 120 | vp = (VIDEO *) malloc (sizeof (VIDEO) + term.t_ncol); 121 | 122 | if (vp == NULL) 123 | exit (1); 124 | 125 | vp->v_flag = 0; 126 | vscreen[i] = vp; 127 | vp = (VIDEO *) malloc (sizeof (VIDEO) + term.t_ncol); 128 | 129 | if (vp == NULL) 130 | exit (1); 131 | 132 | vp->v_flag = 0; 133 | pscreen[i] = vp; 134 | } 135 | } 136 | 137 | /* 138 | * Clean up the virtual terminal system, in anticipation for a return to the 139 | * operating system. Move down to the last line and clear it out (the next 140 | * system prompt will be written in the line). Shut down the channel to the 141 | * terminal. 142 | */ 143 | void vttidy () 144 | { 145 | mlerase (); 146 | movecursor (term.t_nrow, 0); 147 | (*term.t_close) (); 148 | } 149 | 150 | /* 151 | * Set the virtual cursor to the specified row and column on the virtual 152 | * screen. There is no checking for nonsense values; this might be a good idea 153 | * during the early stages. 154 | */ 155 | void vtmove (int row, int col) 156 | { 157 | vtrow = row; 158 | vtcol = col; 159 | } 160 | 161 | /* 162 | * Write a character to the virtual screen. The virtual row and column are 163 | * updated. If the line is too long put a "$" in the last column. This routine 164 | * only puts printing characters into the virtual terminal buffers. Only 165 | * column overflow is checked. 166 | */ 167 | void vtputc (int c) 168 | { 169 | VIDEO *vp; 170 | 171 | vp = vscreen[vtrow]; 172 | 173 | if (vtcol >= term.t_ncol) 174 | { 175 | vtcol = (vtcol + 0x07) & ~0x07; 176 | vp->v_text[term.t_ncol - 1] = '$'; 177 | } 178 | else if (c == '\t') 179 | { 180 | do 181 | { 182 | vtputc (' '); 183 | } 184 | while ((vtcol & 0x07) != 0); 185 | } 186 | else if (c < 0x20 || c == 0x7F) 187 | { 188 | vtputc ('^'); 189 | vtputc (c ^ 0x40); 190 | } 191 | else 192 | vp->v_text[vtcol++] = c; 193 | } 194 | 195 | /* put a character to the virtual screen in an extended line. If we are not 196 | * yet on left edge, don't print it yet. check for overflow on the right 197 | * margin 198 | */ 199 | void vtpute (int c) 200 | { 201 | VIDEO *vp; 202 | 203 | vp = vscreen[vtrow]; 204 | 205 | if (vtcol >= term.t_ncol) 206 | { 207 | vtcol = (vtcol + 0x07) & ~0x07; 208 | vp->v_text[term.t_ncol - 1] = '$'; 209 | } 210 | else if (c == '\t') 211 | { 212 | do 213 | { 214 | vtpute (' '); 215 | } 216 | while (((vtcol + lbound) & 0x07) != 0); 217 | } 218 | else if (c < 0x20 || c == 0x7F) 219 | { 220 | vtpute ('^'); 221 | vtpute (c ^ 0x40); 222 | } 223 | else 224 | { 225 | if (vtcol >= 0) 226 | vp->v_text[vtcol] = c; 227 | ++vtcol; 228 | } 229 | } 230 | 231 | /* 232 | * [In the virtual screen] Erase from the end of the software cursor to the 233 | * end of the line on which the software cursor is located. 234 | */ 235 | void vteeol () 236 | { 237 | VIDEO *vp; 238 | 239 | vp = vscreen[vtrow]; 240 | while (vtcol < term.t_ncol) 241 | vp->v_text[vtcol++] = ' '; 242 | } 243 | 244 | /* 245 | * Make sure that the display is right. This is a three part process. First, 246 | * scan through all of the windows looking for dirty ones. Check the framing, 247 | * and refresh the screen. Second, make sure that "currow" and "curcol" are 248 | * correct for the current window. Third, make the virtual and physical 249 | * screens the same 250 | */ 251 | void update () 252 | { 253 | VIDEO *vp1, *vp2; 254 | LINE *lp; 255 | WINDOW *wp; 256 | int i, j, c; 257 | 258 | if (typahead()) 259 | return; 260 | 261 | /* update the reverse video flags for any mode lines out there */ 262 | for (i = 0; i < term.t_nrow; ++i) 263 | vscreen[i]->v_flag &= ~VFREQ; 264 | 265 | wp = wheadp; 266 | while (wp != NULL) 267 | { 268 | vscreen[wp->w_toprow + wp->w_ntrows]->v_flag |= VFREQ; 269 | wp = wp->w_wndp; 270 | } 271 | 272 | wp = wheadp; 273 | while (wp != NULL) 274 | { 275 | /* Look at any window with update flags set on */ 276 | if (wp->w_flag != 0) 277 | { 278 | /* If not force reframe, check the framing */ 279 | if ((wp->w_flag & WFFORCE) == 0) 280 | { 281 | lp = wp->w_linep; 282 | for (i = 0; i < wp->w_ntrows; ++i) 283 | { 284 | if (lp == wp->w_dotp) 285 | goto out; 286 | if (lp == wp->w_bufp->b_linep) 287 | break; 288 | lp = lforw (lp); 289 | } 290 | } 291 | /* Not acceptable, better compute a new value for the line at the top 292 | * of the window. Then set the "WFHARD" flag to force full redraw */ 293 | i = wp->w_force; 294 | if (i > 0) 295 | { 296 | --i; 297 | if (i >= wp->w_ntrows) 298 | i = wp->w_ntrows - 1; 299 | } 300 | else if (i < 0) 301 | { 302 | i += wp->w_ntrows; 303 | if (i < 0) 304 | i = 0; 305 | } 306 | else 307 | i = wp->w_ntrows / 2; 308 | lp = wp->w_dotp; 309 | while (i != 0 && lback (lp) != wp->w_bufp->b_linep) 310 | { 311 | --i; 312 | lp = lback (lp); 313 | } 314 | wp->w_linep = lp; 315 | wp->w_flag |= WFHARD; /* Force full */ 316 | 317 | out: 318 | /* Try to use reduced update. Mode line update has its own special 319 | * flag. The fast update is used if the only thing to do is within 320 | * the line editing */ 321 | lp = wp->w_linep; 322 | i = wp->w_toprow; 323 | if ((wp->w_flag & ~WFMODE) == WFEDIT) 324 | { 325 | while (lp != wp->w_dotp) 326 | { 327 | ++i; 328 | lp = lforw (lp); 329 | } 330 | vscreen[i]->v_flag |= VFCHG; 331 | vtmove (i, 0); 332 | for (j = 0; j < llength (lp); ++j) 333 | vtputc (lgetc (lp, j)); 334 | vteeol (); 335 | } 336 | else if ((wp->w_flag & (WFEDIT | WFHARD)) != 0) 337 | { 338 | while (i < wp->w_toprow + wp->w_ntrows) 339 | { 340 | vscreen[i]->v_flag |= VFCHG; 341 | vtmove (i, 0); 342 | /* if line has been changed */ 343 | if (lp != wp->w_bufp->b_linep) 344 | { 345 | for (j = 0; j < llength (lp); ++j) 346 | vtputc (lgetc (lp, j)); 347 | lp = lforw (lp); 348 | } 349 | vteeol (); 350 | ++i; 351 | } 352 | } 353 | } 354 | if (wp->w_flag & WFMODE) 355 | modeline(wp); 356 | wp->w_flag = 0; 357 | wp->w_force = 0; 358 | 359 | wp = wp->w_wndp; /* and onward to the next window */ 360 | } 361 | 362 | /* Always recompute the row and column number of the hardware cursor. This 363 | * is the only update for simple moves */ 364 | lp = curwp->w_linep; 365 | currow = curwp->w_toprow; 366 | while (lp != curwp->w_dotp) 367 | { 368 | ++currow; 369 | lp = lforw (lp); 370 | } 371 | 372 | curcol = 0; 373 | i = 0; 374 | while (i < curwp->w_doto) 375 | { 376 | c = lgetc (lp, i++); 377 | if (c == '\t') 378 | curcol |= 0x07; 379 | else if (c < 0x20 || c == 0x7F) 380 | ++curcol; 381 | ++curcol; 382 | } 383 | if (curcol >= term.t_ncol - 1) 384 | { /* extended line */ 385 | /* flag we are extended and changed */ 386 | vscreen[currow]->v_flag |= VFEXT | VFCHG; 387 | updext (); /* and output extended line */ 388 | } 389 | else 390 | lbound = 0; /* not extended line */ 391 | 392 | /* make sure no lines need to be de-extended because the cursor is no 393 | * longer on them */ 394 | 395 | wp = wheadp; 396 | 397 | while (wp != NULL) 398 | { 399 | lp = wp->w_linep; 400 | i = wp->w_toprow; 401 | 402 | while (i < wp->w_toprow + wp->w_ntrows) 403 | { 404 | if (vscreen[i]->v_flag & VFEXT) 405 | { 406 | /* always flag extended lines as changed */ 407 | vscreen[i]->v_flag |= VFCHG; 408 | if ((wp != curwp) || (lp != wp->w_dotp) || 409 | (curcol < term.t_ncol - 1)) 410 | { 411 | vtmove (i, 0); 412 | for (j = 0; j < llength (lp); ++j) 413 | vtputc (lgetc (lp, j)); 414 | vteeol (); 415 | /* this line no longer is extended */ 416 | vscreen[i]->v_flag &= ~VFEXT; 417 | } 418 | } 419 | lp = lforw (lp); 420 | ++i; 421 | } 422 | /* and onward to the next window */ 423 | wp = wp->w_wndp; 424 | } 425 | 426 | /* Special hacking if the screen is garbage. Clear the hardware screen, and 427 | * update your copy to agree with it. Set all the virtual screen change 428 | * bits, to force a full update */ 429 | if (sgarbf != FALSE) 430 | { 431 | for (i = 0; i < term.t_nrow; ++i) 432 | { 433 | vscreen[i]->v_flag |= VFCHG; 434 | vp1 = pscreen[i]; 435 | for (j = 0; j < term.t_ncol; ++j) 436 | vp1->v_text[j] = ' '; 437 | } 438 | 439 | movecursor (0, 0); /* Erase the screen */ 440 | (*term.t_eeop) (); 441 | sgarbf = FALSE; /* Erase-page clears */ 442 | mpresf = FALSE; /* the message area */ 443 | } 444 | /* Make sure that the physical and virtual displays agree. Unlike before, 445 | * the "updateline" code is only called with a line that has been updated 446 | * for sure */ 447 | for (i = 0; i < term.t_nrow; ++i) 448 | { 449 | vp1 = vscreen[i]; 450 | 451 | /* for each line that needs to be updated, or that needs its reverse 452 | * video status changed, call the line updater */ 453 | j = vp1->v_flag; 454 | if (((j & VFCHG) != 0) || (((j & VFREV) == 0) != ((j & VFREQ) == 0))) 455 | { 456 | if (typahead ()) 457 | return; 458 | vp2 = pscreen[i]; 459 | updateline (i, &vp1->v_text[0], &vp2->v_text[0], &vp1->v_flag); 460 | } 461 | } 462 | 463 | /* Finally, update the hardware cursor and flush out buffers */ 464 | movecursor (currow, curcol - lbound); 465 | (*term.t_flush) (); 466 | } 467 | 468 | /* updext: update the extended line which the cursor is currently on at a 469 | * column greater than the terminal width. The line will be scrolled right or 470 | * left to let the user see where the cursor is 471 | */ 472 | void updext () 473 | { 474 | LINE *lp; /* pointer to current line */ 475 | int rcursor; /* real cursor location */ 476 | int j; /* index into line */ 477 | 478 | /* calculate what column the real cursor will end up in */ 479 | rcursor = ((curcol - term.t_ncol) % term.t_scrsiz) + term.t_margin; 480 | lbound = curcol - rcursor + 1; 481 | 482 | /* scan through the line outputing characters to the virtual screen */ 483 | /* once we reach the left edge */ 484 | vtmove (currow, -lbound); /* start scanning offscreen */ 485 | lp = curwp->w_dotp; /* line to output */ 486 | for (j = 0; j < llength (lp); ++j) /* until the end-of-line */ 487 | vtpute (lgetc (lp, j)); 488 | /* truncate the virtual line */ 489 | vteeol (); 490 | /* and put a '$' in column 1 */ 491 | vscreen[currow]->v_text[0] = '$'; 492 | } 493 | 494 | /* 495 | * Update a single line. This does not know how to use insert or delete 496 | * character sequences; we are using VT52 functionality. Update the physical 497 | * row and column variables. It does try an exploit erase to end of line. 498 | */ 499 | void updateline (int row, char vline[], char pline[], short *flags) 500 | { 501 | char *cp1, *cp2, *cp3, *cp4, *cp5; 502 | int nbflag; /* non-blanks to the right flag? */ 503 | int rev; /* reverse video flag */ 504 | int req; /* reverse video request flag */ 505 | 506 | /* set up pointers to virtual and physical lines */ 507 | cp1 = &vline[0]; 508 | cp2 = &pline[0]; 509 | 510 | /* if we need to change the reverse video status of the current line, we 511 | * need to re-write the entire line */ 512 | rev = *flags & VFREV; 513 | req = *flags & VFREQ; 514 | if (rev != req) 515 | { 516 | movecursor (row, 0); /* Go to start of line */ 517 | (*term.t_rev) (req != FALSE); /* set rev video if needed */ 518 | 519 | /* scan through the line and dump it to the screen and the virtual 520 | * screen array */ 521 | cp3 = &vline[term.t_ncol]; 522 | while (cp1 < cp3) 523 | { 524 | (*term.t_putchar) (*cp1); 525 | ++ttcol; 526 | *cp2++ = *cp1++; 527 | } 528 | (*term.t_rev) (FALSE); /* turn rev video off */ 529 | 530 | /* update the needed flags */ 531 | *flags &= ~VFCHG; 532 | if (req) 533 | *flags |= VFREV; 534 | else 535 | *flags &= ~VFREV; 536 | return; 537 | } 538 | 539 | /* advance past any common chars at the left */ 540 | while (cp1 != &vline[term.t_ncol] && cp1[0] == cp2[0]) 541 | { 542 | ++cp1; 543 | ++cp2; 544 | } 545 | 546 | /* This can still happen, even though we only call this routine on changed 547 | * lines. A hard update is always done when a line splits, a massive change 548 | * is done, or a buffer is displayed twice. This optimizes out most of the 549 | * excess updating. A lot of computes are used, but these tend to be hard 550 | * operations that do a lot of update, so I don't really care */ 551 | /* if both lines are the same, no update needs to be done */ 552 | if (cp1 == &vline[term.t_ncol]) 553 | return; 554 | 555 | /* find out if there is a match on the right */ 556 | nbflag = FALSE; 557 | cp3 = &vline[term.t_ncol]; 558 | cp4 = &pline[term.t_ncol]; 559 | 560 | while (cp3[-1] == cp4[-1]) 561 | { 562 | --cp3; 563 | --cp4; 564 | if (cp3[0] != ' ') /* Note if any nonblank */ 565 | nbflag = TRUE; /* in right match */ 566 | } 567 | 568 | cp5 = cp3; 569 | 570 | if (nbflag == FALSE && eolexist == TRUE) 571 | { /* Erase to EOL ? */ 572 | while (cp5 != cp1 && cp5[-1] == ' ') 573 | --cp5; 574 | if (cp3 - cp5 <= 3) /* Use only if erase is */ 575 | cp5 = cp3; /* fewer characters */ 576 | } 577 | movecursor (row, cp1 - &vline[0]); /* Go to start of line */ 578 | 579 | while (cp1 != cp5) 580 | { /* Ordinary */ 581 | (*term.t_putchar) (*cp1); 582 | ++ttcol; 583 | *cp2++ = *cp1++; 584 | } 585 | 586 | if (cp5 != cp3) 587 | { /* Erase */ 588 | (*term.t_eeol) (); 589 | while (cp1 != cp3) 590 | *cp2++ = *cp1++; 591 | } 592 | *flags &= ~VFCHG; /* flag this line is changed */ 593 | } 594 | 595 | /* 596 | * Redisplay the mode line for the window pointed to by the "wp". This is the 597 | * only routine that has any idea of how the modeline is formatted. You can 598 | * change the modeline format by hacking at this routine. Called by "update" 599 | * any time there is a dirty window. 600 | */ 601 | void modeline (WINDOW *wp) 602 | { 603 | BUFFER *bp; 604 | char tline[NLINE]; /* buffer for part of mode line */ 605 | char *cp; 606 | int lchar; /* character to draw line in buffer with */ 607 | int c; 608 | int n; /* cursor position count */ 609 | 610 | n = wp->w_toprow + wp->w_ntrows; /* Location */ 611 | vscreen[n]->v_flag |= VFCHG; /* Redraw next time */ 612 | vtmove (n, 0); /* Seek to right line */ 613 | if (wp == curwp) /* mark the current buffer */ 614 | lchar = '='; 615 | else 616 | if (revexist) 617 | lchar = ' '; 618 | else 619 | lchar = '-'; 620 | 621 | vtputc (lchar); 622 | bp = wp->w_bufp; 623 | 624 | if ((bp->b_flag & BFCHG) != 0) /* "*" if changed */ 625 | vtputc ('*'); 626 | else 627 | vtputc (lchar); 628 | 629 | n = 2; 630 | strncpy (tline, " ErsatzEMACS ", 14); 631 | 632 | cp = &tline[0]; 633 | while ((c = *cp++) != 0) 634 | { 635 | vtputc (c); 636 | ++n; 637 | } 638 | 639 | vtputc (lchar); 640 | vtputc (lchar); 641 | vtputc (' '); 642 | n += 3; 643 | cp = &bp->b_bname[0]; 644 | 645 | while ((c = *cp++) != 0) 646 | { 647 | vtputc (c); 648 | ++n; 649 | } 650 | 651 | vtputc (' '); 652 | vtputc (lchar); 653 | vtputc (lchar); 654 | n += 3; 655 | 656 | if (bp->b_fname[0] != 0) /* File name */ 657 | { 658 | vtputc (' '); 659 | ++n; 660 | cp = "File: "; 661 | 662 | while ((c = *cp++) != 0) 663 | { 664 | vtputc (c); 665 | ++n; 666 | } 667 | 668 | cp = &bp->b_fname[0]; 669 | 670 | while ((c = *cp++) != 0) 671 | { 672 | vtputc (c); 673 | ++n; 674 | } 675 | 676 | vtputc (' '); 677 | ++n; 678 | } 679 | 680 | while (n < term.t_ncol) /* Pad to full width */ 681 | { 682 | vtputc (lchar); 683 | ++n; 684 | } 685 | } 686 | 687 | /* update all the mode lines 688 | */ 689 | void upmode () 690 | { 691 | WINDOW *wp; 692 | 693 | wp = wheadp; 694 | while (wp != NULL) 695 | { 696 | wp->w_flag |= WFMODE; 697 | wp = wp->w_wndp; 698 | } 699 | } 700 | 701 | /* 702 | * Ask a yes or no question in the message line. Return either TRUE, FALSE, or 703 | * ABORT. The ABORT status is returned if the user bumps out of the question 704 | * with a ^G. Used any time a confirmation is required. 705 | */ 706 | int mlyesno (char *prompt) 707 | { 708 | char c; /* input character */ 709 | char buf[NPAT]; /* prompt to user */ 710 | 711 | for (;;) 712 | { 713 | /* build and prompt the user */ 714 | strncpy (buf, prompt, 60); 715 | strncat (buf, " [y/n]? ", 9); 716 | mlwrite (buf); 717 | 718 | /* get the responce */ 719 | c = (*term.t_getchar) (); 720 | if (c == BELL) /* Bail out! */ 721 | return (ABORT); 722 | if (c == 'y' || c == 'Y') 723 | return (TRUE); 724 | if (c == 'n' || c == 'N') 725 | return (FALSE); 726 | } 727 | } 728 | 729 | /* A more generalized prompt/reply function allowing the caller to specify the 730 | * proper terminator. If the terminator is not a return ('\n') it will echo as 731 | * "" 732 | */ 733 | int mlreplyt (char *prompt, char *buf, int nbuf, char eolchar) 734 | { 735 | int cpos, i, c; 736 | 737 | cpos = 0; 738 | 739 | if (kbdmop != NULL) 740 | { 741 | while ((c = *kbdmop++) != '\0') 742 | buf[cpos++] = c; 743 | buf[cpos] = 0; 744 | if (buf[0] == 0) 745 | return (FALSE); 746 | return (TRUE); 747 | } 748 | mlwrite (prompt); 749 | 750 | for (;;) 751 | { 752 | /* get a character from the user. if it is a change it to a */ 753 | c = (*term.t_getchar) (); 754 | if (c == 0x0d) 755 | c = '\n'; 756 | 757 | if (c == eolchar) 758 | { 759 | buf[cpos++] = 0; 760 | 761 | if (kbdmip != NULL) 762 | { 763 | if (kbdmip + cpos > &kbdm[NKBDM - 3]) 764 | { 765 | ctrlg (FALSE, 0); 766 | (*term.t_flush) (); 767 | return (ABORT); 768 | } 769 | for (i = 0; i < cpos; ++i) 770 | *kbdmip++ = buf[i]; 771 | } 772 | (*term.t_putchar) ('\r'); 773 | ttcol = 0; 774 | (*term.t_flush) (); 775 | 776 | if (buf[0] == 0) 777 | return (FALSE); 778 | 779 | return (TRUE); 780 | 781 | } 782 | else if (c == 0x07) 783 | { /* Bell, abort */ 784 | (*term.t_putchar) ('^'); 785 | (*term.t_putchar) ('G'); 786 | ttcol += 2; 787 | ctrlg (FALSE, 0); 788 | (*term.t_flush) (); 789 | return (ABORT); 790 | } 791 | else if (c == 0x7F || c == 0x08) 792 | { /* rubout/erase */ 793 | if (cpos != 0) 794 | { 795 | (*term.t_putchar) ('\b'); 796 | (*term.t_putchar) (' '); 797 | (*term.t_putchar) ('\b'); 798 | --ttcol; 799 | 800 | if (buf[--cpos] < 0x20) 801 | { 802 | (*term.t_putchar) ('\b'); 803 | (*term.t_putchar) (' '); 804 | (*term.t_putchar) ('\b'); 805 | --ttcol; 806 | } 807 | if (buf[cpos] == '\n') 808 | { 809 | (*term.t_putchar) ('\b'); 810 | (*term.t_putchar) ('\b'); 811 | (*term.t_putchar) (' '); 812 | (*term.t_putchar) (' '); 813 | (*term.t_putchar) ('\b'); 814 | (*term.t_putchar) ('\b'); 815 | --ttcol; 816 | --ttcol; 817 | } 818 | (*term.t_flush) (); 819 | } 820 | } 821 | else if (c == 0x15) 822 | { /* C-U, kill */ 823 | while (cpos != 0) 824 | { 825 | (*term.t_putchar) ('\b'); 826 | (*term.t_putchar) (' '); 827 | (*term.t_putchar) ('\b'); 828 | --ttcol; 829 | if (buf[--cpos] < 0x20) 830 | { 831 | (*term.t_putchar) ('\b'); 832 | (*term.t_putchar) (' '); 833 | (*term.t_putchar) ('\b'); 834 | --ttcol; 835 | } 836 | } 837 | (*term.t_flush) (); 838 | } 839 | else 840 | { 841 | if (cpos < nbuf - 1) 842 | { 843 | buf[cpos++] = c; 844 | if ((c < ' ') && (c != '\n')) 845 | { 846 | (*term.t_putchar) ('^'); 847 | ++ttcol; 848 | c ^= 0x40; 849 | } 850 | if (c != '\n') 851 | (*term.t_putchar) (c); 852 | else 853 | { /* put out for */ 854 | (*term.t_putchar) ('<'); 855 | (*term.t_putchar) ('N'); 856 | (*term.t_putchar) ('L'); 857 | (*term.t_putchar) ('>'); 858 | ttcol += 3; 859 | } 860 | ++ttcol; 861 | (*term.t_flush) (); 862 | } 863 | } 864 | } 865 | } 866 | 867 | /* 868 | * Write a prompt into the message line, then read back a response. Keep track 869 | * of the physical position of the cursor. If we are in a keyboard macro throw 870 | * the prompt away, and return the remembered response. This lets macros run 871 | * at full speed. The reply is always terminated by a carriage return. Handle 872 | * erase, kill, and abort keys. 873 | */ 874 | int mlreply (char *prompt, char *buf, int nbuf) 875 | { 876 | return (mlreplyt (prompt, buf, nbuf, '\n')); 877 | } 878 | 879 | /* 880 | * Write a message into the message line. Keep track of the physical cursor 881 | * position. A small class of printf like format items is handled. Assumes the 882 | * stack grows down; this assumption is made by the "++" in the argument scan 883 | * loop. Set the "message line" flag TRUE. 884 | */ 885 | void mlwrite (char *fmt, int arg) 886 | { 887 | int c; 888 | char *ap; 889 | 890 | if (eolexist == FALSE) 891 | { 892 | mlerase (); 893 | (*term.t_flush) (); 894 | } 895 | movecursor (term.t_nrow, 0); 896 | ap = (char *) &arg; 897 | while ((c = *fmt++) != 0) 898 | { 899 | if (c != '%') 900 | { 901 | (*term.t_putchar) (c); 902 | ++ttcol; 903 | } 904 | else 905 | { 906 | c = *fmt++; 907 | switch (c) 908 | { 909 | case 'd': 910 | mlputi (*(int *) ap, 10); 911 | ap += sizeof (int); 912 | break; 913 | 914 | case 'o': 915 | mlputi (*(int *) ap, 8); 916 | ap += sizeof (int); 917 | break; 918 | 919 | case 'x': 920 | mlputi (*(int *) ap, 16); 921 | ap += sizeof (int); 922 | break; 923 | 924 | case 'D': 925 | mlputli (*(long *) ap, 10); 926 | ap += sizeof (long); 927 | break; 928 | 929 | case 's': 930 | mlputs (*(char **) &ap); 931 | ap += sizeof (char *); 932 | break; 933 | 934 | case 'c': 935 | (*term.t_putchar) (*ap); 936 | ++ttcol; 937 | ap += sizeof (char *); 938 | break; 939 | 940 | default: 941 | (*term.t_putchar) (c); 942 | ++ttcol; 943 | } 944 | } 945 | } 946 | if (eolexist == TRUE) 947 | (*term.t_eeol) (); 948 | (*term.t_flush) (); 949 | mpresf = TRUE; 950 | } 951 | 952 | /* 953 | * Write out a string. Update the physical cursor position. This assumes that 954 | * the characters in the string all have width "1"; if this is not the case 955 | * things will get screwed up a little. 956 | */ 957 | void mlputs (char *s) 958 | { 959 | int c; 960 | 961 | while ((c = *s++) != 0) 962 | { 963 | (*term.t_putchar) (c); 964 | ++ttcol; 965 | } 966 | } 967 | 968 | /* 969 | * Write out an integer, in the specified radix. Update the physical cursor 970 | * position. This will not handle any negative numbers; maybe it should. 971 | */ 972 | void mlputi (int i, int r) 973 | { 974 | int q; 975 | static char hexdigits[] = "0123456789abcdef"; 976 | 977 | if (i < 0) 978 | { 979 | i = -i; 980 | (*term.t_putchar) ('-'); 981 | } 982 | q = i / r; 983 | 984 | if (q != 0) 985 | mlputi (q, r); 986 | 987 | (*term.t_putchar) (hexdigits[i % r]); 988 | ++ttcol; 989 | } 990 | 991 | /* 992 | * do the same except as a long integer. 993 | */ 994 | void mlputli (long l, int r) 995 | { 996 | long q; 997 | 998 | if (l < 0) 999 | { 1000 | l = -l; 1001 | (*term.t_putchar) ('-'); 1002 | } 1003 | q = l / r; 1004 | 1005 | if (q != 0) 1006 | mlputli (q, r); 1007 | 1008 | (*term.t_putchar) ((int) (l % r) + '0'); 1009 | ++ttcol; 1010 | } 1011 | 1012 | --------------------------------------------------------------------------------