From a9843085ec916c175bd245a8398f30e6cc03f984 Mon Sep 17 00:00:00 2001 From: andy Date: Fri, 24 Aug 2012 23:26:29 +1200 Subject: [PATCH] Added mg from an OpenBSD mirror site. Many thanks to the OpenBSD team and the mg devs. (Arrrrgh... Theo... please don't hit me.... ;) ) --- README | 15 + mg/Makefile | 34 ++ mg/autoexec.c | 111 +++++ mg/basic.c | 532 ++++++++++++++++++++++++ mg/buffer.c | 863 +++++++++++++++++++++++++++++++++++++++ mg/chrdef.h | 81 ++++ mg/cinfo.c | 154 +++++++ mg/cmode.c | 528 ++++++++++++++++++++++++ mg/cscope.c | 609 +++++++++++++++++++++++++++ mg/def.h | 707 ++++++++++++++++++++++++++++++++ mg/dir.c | 77 ++++ mg/dired.c | 779 +++++++++++++++++++++++++++++++++++ mg/display.c | 1064 ++++++++++++++++++++++++++++++++++++++++++++++++ mg/echo.c | 964 +++++++++++++++++++++++++++++++++++++++++++ mg/extend.c | 963 +++++++++++++++++++++++++++++++++++++++++++ mg/file.c | 715 ++++++++++++++++++++++++++++++++ mg/fileio.c | 754 ++++++++++++++++++++++++++++++++++ mg/funmap.c | 281 +++++++++++++ mg/funmap.h | 9 + mg/grep.c | 365 +++++++++++++++++ mg/help.c | 231 +++++++++++ mg/kbd.c | 436 ++++++++++++++++++++ mg/kbd.h | 58 +++ mg/key.h | 14 + mg/keymap.c | 567 ++++++++++++++++++++++++++ mg/line.c | 635 +++++++++++++++++++++++++++++ mg/macro.c | 106 +++++ mg/macro.h | 21 + mg/main.c | 245 +++++++++++ mg/match.c | 185 +++++++++ mg/mg.1 | 956 +++++++++++++++++++++++++++++++++++++++++++ mg/modes.c | 165 ++++++++ mg/paragraph.c | 362 ++++++++++++++++ mg/pathnames.h | 11 + mg/random.c | 496 ++++++++++++++++++++++ mg/re_search.c | 615 ++++++++++++++++++++++++++++ mg/region.c | 597 +++++++++++++++++++++++++++ mg/spawn.c | 48 +++ mg/sysdef.h | 30 ++ mg/tags.c | 533 ++++++++++++++++++++++++ mg/theo.c | 192 +++++++++ mg/tty.c | 447 ++++++++++++++++++++ mg/ttydef.h | 25 ++ mg/ttyio.c | 225 ++++++++++ mg/ttykbd.c | 89 ++++ mg/tutorial | 349 ++++++++++++++++ mg/undo.c | 585 ++++++++++++++++++++++++++ mg/version.c | 24 ++ mg/window.c | 428 +++++++++++++++++++ mg/word.c | 346 ++++++++++++++++ mg/yank.c | 264 ++++++++++++ 51 files changed, 18890 insertions(+) create mode 100644 mg/Makefile create mode 100644 mg/autoexec.c create mode 100644 mg/basic.c create mode 100644 mg/buffer.c create mode 100644 mg/chrdef.h create mode 100644 mg/cinfo.c create mode 100644 mg/cmode.c create mode 100644 mg/cscope.c create mode 100644 mg/def.h create mode 100644 mg/dir.c create mode 100644 mg/dired.c create mode 100644 mg/display.c create mode 100644 mg/echo.c create mode 100644 mg/extend.c create mode 100644 mg/file.c create mode 100644 mg/fileio.c create mode 100644 mg/funmap.c create mode 100644 mg/funmap.h create mode 100644 mg/grep.c create mode 100644 mg/help.c create mode 100644 mg/kbd.c create mode 100644 mg/kbd.h create mode 100644 mg/key.h create mode 100644 mg/keymap.c create mode 100644 mg/line.c create mode 100644 mg/macro.c create mode 100644 mg/macro.h create mode 100644 mg/main.c create mode 100644 mg/match.c create mode 100644 mg/mg.1 create mode 100644 mg/modes.c create mode 100644 mg/paragraph.c create mode 100644 mg/pathnames.h create mode 100644 mg/random.c create mode 100644 mg/re_search.c create mode 100644 mg/region.c create mode 100644 mg/spawn.c create mode 100644 mg/sysdef.h create mode 100644 mg/tags.c create mode 100644 mg/theo.c create mode 100644 mg/tty.c create mode 100644 mg/ttydef.h create mode 100644 mg/ttyio.c create mode 100644 mg/ttykbd.c create mode 100644 mg/tutorial create mode 100644 mg/undo.c create mode 100644 mg/version.c create mode 100644 mg/window.c create mode 100644 mg/word.c create mode 100644 mg/yank.c diff --git a/README b/README index a69390f..b3a0eca 100644 --- a/README +++ b/README @@ -4,6 +4,21 @@ This repo is for the (eventual) storage of a public-domain readline-and-command-history implementation. +Update - 24th Aug 2012 - +Added the mg editor files from OpenBSD. +I obtained the code from here - +ftp://ftp.cc.uoc.gr/mirrors/OpenBSD/src/usr.bin/mg/ + +( It's not so much the editor itself that is the main interest here, +but the line-management code. ) + +NOTE - in the mg directory, the file "theo.c" is NOT +"public domain" ( but it HAD to be included because of the +humour...... ;) ) + +Many thanks to the OpenBSD team and the mg devs +(hi Theo - arrrrrrgh, please don't hit me........... ;) ) + - mooseman diff --git a/mg/Makefile b/mg/Makefile new file mode 100644 index 0000000..5b235e5 --- /dev/null +++ b/mg/Makefile @@ -0,0 +1,34 @@ +# $OpenBSD: Makefile,v 1.27 2012/06/18 07:13:26 jasper Exp $ + +PROG= mg + +LDADD+= -lcurses -lutil +DPADD+= ${LIBCURSES} ${LIBUTIL} + +# (Common) compile-time options: +# +# FKEYS -- add support for function key sequences. +# REGEX -- create regular expression functions. +# STARTUP -- look for and handle initialization file. +# XKEYS -- use termcap function key definitions. +# note: XKEYS and bsmap mode do _not_ get along. +# +CFLAGS+=-Wall -DFKEYS -DREGEX -DXKEYS + +SRCS= autoexec.c basic.c buffer.c cinfo.c dir.c display.c \ + echo.c extend.c file.c fileio.c funmap.c help.c kbd.c keymap.c \ + line.c macro.c main.c match.c modes.c paragraph.c random.c \ + re_search.c region.c search.c spawn.c tty.c ttyio.c ttykbd.c \ + undo.c version.c window.c word.c yank.c + +# +# More or less standalone extensions. +# +SRCS+= cmode.c cscope.c dired.c grep.c tags.c theo.c + +afterinstall: + ${INSTALL} -d ${DESTDIR}${DOCDIR}/mg + ${INSTALL} -m ${DOCMODE} -c ${.CURDIR}/tutorial \ + ${DESTDIR}${DOCDIR}/mg + + diff --git a/mg/autoexec.c b/mg/autoexec.c new file mode 100644 index 0000000..ad3b08d --- /dev/null +++ b/mg/autoexec.c @@ -0,0 +1,111 @@ +/* $OpenBSD: autoexec.c,v 1.14 2007/02/08 21:40:03 kjell Exp $ */ +/* this file is in the public domain */ +/* Author: Vincent Labrecque April 2002 */ + +#include "def.h" +#include "funmap.h" + +#include + +struct autoexec { + SLIST_ENTRY(autoexec) next; /* link in the linked list */ + const char *pattern; /* Pattern to match to filenames */ + PF fp; +}; + +static SLIST_HEAD(, autoexec) autos; +static int ready; + + +#define AUTO_GROW 8 +/* + * Return a NULL terminated array of function pointers to be called + * when we open a file that matches . The list must be free(ed) + * after use. + */ +PF * +find_autoexec(const char *fname) +{ + PF *pfl, *npfl; + int have, used; + struct autoexec *ae; + + if (!ready) + return (NULL); + + pfl = NULL; + have = 0; + used = 0; + SLIST_FOREACH(ae, &autos, next) { + if (fnmatch(ae->pattern, fname, 0) == 0) { + if (used >= have) { + npfl = realloc(pfl, (have + AUTO_GROW + 1) * + sizeof(PF)); + if (npfl == NULL) + panic("out of memory"); + pfl = npfl; + have += AUTO_GROW; + } + pfl[used++] = ae->fp; + } + } + if (used) + pfl[used] = NULL; + + return (pfl); +} + +int +add_autoexec(const char *pattern, const char *func) +{ + PF fp; + struct autoexec *ae; + + if (!ready) { + SLIST_INIT(&autos); + ready = 1; + } + fp = name_function(func); + if (fp == NULL) + return (FALSE); + ae = malloc(sizeof(*ae)); + if (ae == NULL) + return (FALSE); + ae->fp = fp; + ae->pattern = strdup(pattern); + if (ae->pattern == NULL) { + free(ae); + return (FALSE); + } + SLIST_INSERT_HEAD(&autos, ae, next); + + return (TRUE); +} + +/* + * Register an auto-execute hook; that is, specify a filename pattern + * (conforming to the shell's filename globbing rules) and an associated + * function to execute when a file matching the specified pattern + * is read into a buffer. +*/ +/* ARGSUSED */ +int +auto_execute(int f, int n) +{ + char patbuf[128], funcbuf[128], *patp, *funcp; + int s; + + if ((patp = eread("Filename pattern: ", patbuf, sizeof(patbuf), + EFNEW | EFCR)) == NULL) + return (ABORT); + else if (patp[0] == '\0') + return (FALSE); + if ((funcp = eread("Execute: ", funcbuf, sizeof(funcbuf), + EFNEW | EFCR | EFFUNC)) == NULL) + return (ABORT); + else if (funcp[0] == '\0') + return (FALSE); + if ((s = add_autoexec(patp, funcp)) != TRUE) + return (s); + return (TRUE); +} diff --git a/mg/basic.c b/mg/basic.c new file mode 100644 index 0000000..160d9c1 --- /dev/null +++ b/mg/basic.c @@ -0,0 +1,532 @@ +/* $OpenBSD: basic.c,v 1.37 2012/06/18 09:26:03 lum Exp $ */ + +/* This file is in the public domain */ + +/* + * Basic cursor motion commands. + * + * The routines in this file are the basic + * command functions for moving the cursor around on + * the screen, setting mark, and swapping dot with + * mark. Only moves between lines, which might make the + * current buffer framing bad, are hard. + */ +#include "def.h" + +#include + +/* + * Go to beginning of line. + */ +/* ARGSUSED */ +int +gotobol(int f, int n) +{ + curwp->w_doto = 0; + return (TRUE); +} + +/* + * Move cursor backwards. Do the + * right thing if the count is less than + * 0. Error if you try to move back from + * the beginning of the buffer. + */ +/* ARGSUSED */ +int +backchar(int f, int n) +{ + struct line *lp; + + if (n < 0) + return (forwchar(f, -n)); + while (n--) { + if (curwp->w_doto == 0) { + if ((lp = lback(curwp->w_dotp)) == curbp->b_headp) { + if (!(f & FFRAND)) + ewprintf("Beginning of buffer"); + return (FALSE); + } + curwp->w_dotp = lp; + curwp->w_doto = llength(lp); + curwp->w_rflag |= WFMOVE; + curwp->w_dotline--; + } else + curwp->w_doto--; + } + return (TRUE); +} + +/* + * Go to end of line. + */ +/* ARGSUSED */ +int +gotoeol(int f, int n) +{ + curwp->w_doto = llength(curwp->w_dotp); + return (TRUE); +} + +/* + * Move cursor forwards. Do the + * right thing if the count is less than + * 0. Error if you try to move forward + * from the end of the buffer. + */ +/* ARGSUSED */ +int +forwchar(int f, int n) +{ + if (n < 0) + return (backchar(f, -n)); + while (n--) { + if (curwp->w_doto == llength(curwp->w_dotp)) { + curwp->w_dotp = lforw(curwp->w_dotp); + if (curwp->w_dotp == curbp->b_headp) { + curwp->w_dotp = lback(curwp->w_dotp); + if (!(f & FFRAND)) + ewprintf("End of buffer"); + return (FALSE); + } + curwp->w_doto = 0; + curwp->w_dotline++; + curwp->w_rflag |= WFMOVE; + } else + curwp->w_doto++; + } + return (TRUE); +} + +/* + * Go to the beginning of the + * buffer. Setting WFFULL is conservative, + * but almost always the case. + */ +int +gotobob(int f, int n) +{ + (void) setmark(f, n); + curwp->w_dotp = bfirstlp(curbp); + curwp->w_doto = 0; + curwp->w_rflag |= WFFULL; + curwp->w_dotline = 1; + return (TRUE); +} + +/* + * Go to the end of the buffer. Leave dot 3 lines from the bottom of the + * window if buffer length is longer than window length; same as emacs. + * Setting WFFULL is conservative, but almost always the case. + */ +int +gotoeob(int f, int n) +{ + struct line *lp; + + (void) setmark(f, n); + curwp->w_dotp = blastlp(curbp); + curwp->w_doto = llength(curwp->w_dotp); + curwp->w_dotline = curwp->w_bufp->b_lines; + + lp = curwp->w_dotp; + n = curwp->w_ntrows - 3; + + if (n < curwp->w_bufp->b_lines && n >= 3) { + while (n--) + curwp->w_dotp = lback(curwp->w_dotp); + + curwp->w_linep = curwp->w_dotp; + curwp->w_dotp = lp; + } + curwp->w_rflag |= WFFULL; + return (TRUE); +} + +/* + * Move forward by full lines. + * If the number of lines to move is less + * than zero, call the backward line function to + * actually do it. The last command controls how + * the goal column is set. + */ +/* ARGSUSED */ +int +forwline(int f, int n) +{ + struct line *dlp; + + if (n < 0) + return (backline(f | FFRAND, -n)); + if ((dlp = curwp->w_dotp) == curbp->b_headp) + return(TRUE); + if ((lastflag & CFCPCN) == 0) /* Fix goal. */ + setgoal(); + thisflag |= CFCPCN; + if (n == 0) + return (TRUE); + while (n--) { + dlp = lforw(dlp); + if (dlp == curbp->b_headp) { + curwp->w_dotp = lback(dlp); + curwp->w_doto = llength(curwp->w_dotp); + curwp->w_rflag |= WFMOVE; + return (TRUE); + } + curwp->w_dotline++; + } + curwp->w_rflag |= WFMOVE; + curwp->w_dotp = dlp; + curwp->w_doto = getgoal(dlp); + + return (TRUE); +} + +/* + * This function is like "forwline", but + * goes backwards. The scheme is exactly the same. + * Check for arguments that are less than zero and + * call your alternate. Figure out the new line and + * call "movedot" to perform the motion. + */ +/* ARGSUSED */ +int +backline(int f, int n) +{ + struct line *dlp; + + if (n < 0) + return (forwline(f | FFRAND, -n)); + if ((lastflag & CFCPCN) == 0) /* Fix goal. */ + setgoal(); + thisflag |= CFCPCN; + dlp = curwp->w_dotp; + while (n-- && lback(dlp) != curbp->b_headp) { + dlp = lback(dlp); + curwp->w_dotline--; + } + curwp->w_dotp = dlp; + curwp->w_doto = getgoal(dlp); + curwp->w_rflag |= WFMOVE; + return (TRUE); +} + +/* + * Set the current goal column, which is saved in the external variable + * "curgoal", to the current cursor column. The column is never off + * the edge of the screen; it's more like display then show position. + */ +void +setgoal(void) +{ + curgoal = getcolpos(); /* Get the position. */ + /* we can now display past end of display, don't chop! */ +} + +/* + * This routine looks at a line (pointed + * to by the LINE pointer "dlp") and the current + * vertical motion goal column (set by the "setgoal" + * routine above) and returns the best offset to use + * when a vertical motion is made into the line. + */ +int +getgoal(struct line *dlp) +{ + int c, i, col = 0; + char tmp[5]; + + + for (i = 0; i < llength(dlp); i++) { + c = lgetc(dlp, i); + if (c == '\t' +#ifdef NOTAB + && !(curbp->b_flag & BFNOTAB) +#endif + ) { + col |= 0x07; + col++; + } else if (ISCTRL(c) != FALSE) { + col += 2; + } else if (isprint(c)) + col++; + else { + col += snprintf(tmp, sizeof(tmp), "\\%o", c); + } + if (col > curgoal) + break; + } + return (i); +} + +/* + * Scroll forward by a specified number + * of lines, or by a full page if no argument. + * The "2" is the window overlap (this is the default + * value from ITS EMACS). Because the top line in + * the window is zapped, we have to do a hard + * update and get it back. + */ +/* ARGSUSED */ +int +forwpage(int f, int n) +{ + struct line *lp; + + if (!(f & FFARG)) { + n = curwp->w_ntrows - 2; /* Default scroll. */ + if (n <= 0) /* Forget the overlap */ + n = 1; /* if tiny window. */ + } else if (n < 0) + return (backpage(f | FFRAND, -n)); + + lp = curwp->w_linep; + while (n--) + if ((lp = lforw(lp)) == curbp->b_headp) { + ttbeep(); + ewprintf("End of buffer"); + return(TRUE); + } + + curwp->w_linep = lp; + curwp->w_rflag |= WFFULL; + + /* if in current window, don't move dot */ + for (n = curwp->w_ntrows; n-- && lp != curbp->b_headp; lp = lforw(lp)) + if (lp == curwp->w_dotp) + return (TRUE); + + /* Advance the dot the slow way, for line nos */ + while (curwp->w_dotp != curwp->w_linep) { + curwp->w_dotp = lforw(curwp->w_dotp); + curwp->w_dotline++; + } + curwp->w_doto = 0; + return (TRUE); +} + +/* + * This command is like "forwpage", + * but it goes backwards. The "2", like above, + * is the overlap between the two windows. The + * value is from the ITS EMACS manual. The + * hard update is done because the top line in + * the window is zapped. + */ +/* ARGSUSED */ +int +backpage(int f, int n) +{ + struct line *lp, *lp2; + + if (!(f & FFARG)) { + n = curwp->w_ntrows - 2; /* Default scroll. */ + if (n <= 0) /* Don't blow up if the */ + return (backline(f, 1));/* window is tiny. */ + } else if (n < 0) + return (forwpage(f | FFRAND, -n)); + + lp = lp2 = curwp->w_linep; + + while (n-- && lback(lp) != curbp->b_headp) { + lp = lback(lp); + } + if (lp == curwp->w_linep) { + ttbeep(); + ewprintf("Beginning of buffer"); + } + curwp->w_linep = lp; + curwp->w_rflag |= WFFULL; + + /* if in current window, don't move dot */ + for (n = curwp->w_ntrows; n-- && lp != curbp->b_headp; lp = lforw(lp)) + if (lp == curwp->w_dotp) + return (TRUE); + + lp2 = lforw(lp2); + + /* Move the dot the slow way, for line nos */ + while (curwp->w_dotp != lp2) { + if (curwp->w_dotline <= curwp->w_ntrows) + return (TRUE); + curwp->w_dotp = lback(curwp->w_dotp); + curwp->w_dotline--; + } + curwp->w_doto = 0; + return (TRUE); +} + +/* + * These functions are provided for compatibility with Gosling's Emacs. They + * are used to scroll the display up (or down) one line at a time. + */ +int +forw1page(int f, int n) +{ + if (!(f & FFARG)) { + n = 1; + f = FFUNIV; + } + forwpage(f | FFRAND, n); + return (TRUE); +} + +int +back1page(int f, int n) +{ + if (!(f & FFARG)) { + n = 1; + f = FFUNIV; + } + backpage(f | FFRAND, n); + return (TRUE); +} + +/* + * Page the other window. Check to make sure it exists, then + * nextwind, forwpage and restore window pointers. + */ +int +pagenext(int f, int n) +{ + struct mgwin *wp; + + if (wheadp->w_wndp == NULL) { + ewprintf("No other window"); + return (FALSE); + } + wp = curwp; + (void) nextwind(f, n); + (void) forwpage(f, n); + curwp = wp; + curbp = wp->w_bufp; + return (TRUE); +} + +/* + * Internal set mark routine, used by other functions (daveb). + */ +void +isetmark(void) +{ + curwp->w_markp = curwp->w_dotp; + curwp->w_marko = curwp->w_doto; + curwp->w_markline = curwp->w_dotline; +} + +/* + * Set the mark in the current window + * to the value of dot. A message is written to + * the echo line. (ewprintf knows about macros) + */ +/* ARGSUSED */ +int +setmark(int f, int n) +{ + isetmark(); + ewprintf("Mark set"); + return (TRUE); +} + +/* Clear the mark, if set. */ +/* ARGSUSED */ +int +clearmark(int f, int n) +{ + if (!curwp->w_markp) + return (FALSE); + + curwp->w_markp = NULL; + curwp->w_marko = 0; + curwp->w_markline = 0; + + return (TRUE); +} + +/* + * Swap the values of "dot" and "mark" in + * the current window. This is pretty easy, because + * all of the hard work gets done by the standard routine + * that moves the mark about. The only possible + * error is "no mark". + */ +/* ARGSUSED */ +int +swapmark(int f, int n) +{ + struct line *odotp; + int odoto, odotline; + + if (curwp->w_markp == NULL) { + ewprintf("No mark in this window"); + return (FALSE); + } + odotp = curwp->w_dotp; + odoto = curwp->w_doto; + odotline = curwp->w_dotline; + curwp->w_dotp = curwp->w_markp; + curwp->w_doto = curwp->w_marko; + curwp->w_dotline = curwp->w_markline; + curwp->w_markp = odotp; + curwp->w_marko = odoto; + curwp->w_markline = odotline; + curwp->w_rflag |= WFMOVE; + return (TRUE); +} + +/* + * Go to a specific line, mostly for + * looking up errors in C programs, which give the + * error a line number. If an argument is present, then + * it is the line number, else prompt for a line number + * to use. + */ +/* ARGSUSED */ +int +gotoline(int f, int n) +{ + struct line *clp; + char buf[32], *bufp; + const char *err; + + if (!(f & FFARG)) { + if ((bufp = eread("Goto line: ", buf, sizeof(buf), + EFNUL | EFNEW | EFCR)) == NULL) + return (ABORT); + if (bufp[0] == '\0') + return (ABORT); + n = (int)strtonum(buf, INT_MIN, INT_MAX, &err); + if (err) { + ewprintf("Line number %s", err); + return (FALSE); + } + } + if (n >= 0) { + if (n == 0) + n++; + curwp->w_dotline = n; + clp = lforw(curbp->b_headp); /* "clp" is first line */ + while (--n > 0) { + if (lforw(clp) == curbp->b_headp) { + curwp->w_dotline = curwp->w_bufp->b_lines; + break; + } + clp = lforw(clp); + } + } else { + curwp->w_dotline = curwp->w_bufp->b_lines + n; + clp = lback(curbp->b_headp); /* "clp" is last line */ + while (n < 0) { + if (lback(clp) == curbp->b_headp) { + curwp->w_dotline = 1; + break; + } + clp = lback(clp); + n++; + } + } + curwp->w_dotp = clp; + curwp->w_doto = 0; + curwp->w_rflag |= WFMOVE; + return (TRUE); +} diff --git a/mg/buffer.c b/mg/buffer.c new file mode 100644 index 0000000..0a4254c --- /dev/null +++ b/mg/buffer.c @@ -0,0 +1,863 @@ +/* $OpenBSD: buffer.c,v 1.78 2012/03/14 13:56:35 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Buffer handling. + */ + +#include "def.h" +#include "kbd.h" /* needed for modes */ + +#include +#include + +static struct buffer *makelist(void); +static struct buffer *bnew(const char *); + +static int usebufname(const char *); + +/* Flag for global working dir */ +extern int globalwd; + +/* ARGSUSED */ +int +togglereadonly(int f, int n) +{ + int s; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (!(curbp->b_flag & BFREADONLY)) + curbp->b_flag |= BFREADONLY; + else { + curbp->b_flag &= ~BFREADONLY; + if (curbp->b_flag & BFCHG) + ewprintf("Warning: Buffer was modified"); + } + curwp->w_rflag |= WFMODE; + + return (TRUE); +} + +/* Switch to the named buffer. + * If no name supplied, switch to the default (alternate) buffer. + */ +int +usebufname(const char *bufp) +{ + struct buffer *bp; + + if (bufp == NULL) + return (ABORT); + if (bufp[0] == '\0' && curbp->b_altb != NULL) + bp = curbp->b_altb; + else if ((bp = bfind(bufp, TRUE)) == NULL) + return (FALSE); + + /* and put it in current window */ + curbp = bp; + return (showbuffer(bp, curwp, WFFRAME | WFFULL)); +} + +/* + * Attach a buffer to a window. The values of dot and mark come + * from the buffer if the use count is 0. Otherwise, they come + * from some other window. *scratch* is the default alternate + * buffer. + */ +/* ARGSUSED */ +int +usebuffer(int f, int n) +{ + char bufn[NBUFN], *bufp; + + /* Get buffer to use from user */ + if ((curbp->b_altb == NULL) && + ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL)) + bufp = eread("Switch to buffer: ", bufn, NBUFN, EFNEW | EFBUF); + else + bufp = eread("Switch to buffer: (default %s) ", bufn, NBUFN, + EFNUL | EFNEW | EFBUF, curbp->b_altb->b_bname); + + return (usebufname(bufp)); +} + +/* + * pop to buffer asked for by the user. + */ +/* ARGSUSED */ +int +poptobuffer(int f, int n) +{ + struct buffer *bp; + struct mgwin *wp; + char bufn[NBUFN], *bufp; + + /* Get buffer to use from user */ + if ((curbp->b_altb == NULL) && + ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL)) + bufp = eread("Switch to buffer in other window: ", bufn, NBUFN, + EFNEW | EFBUF); + else + bufp = eread("Switch to buffer in other window: (default %s) ", + bufn, NBUFN, EFNUL | EFNEW | EFBUF, curbp->b_altb->b_bname); + if (bufp == NULL) + return (ABORT); + if (bufp[0] == '\0' && curbp->b_altb != NULL) + bp = curbp->b_altb; + else if ((bp = bfind(bufn, TRUE)) == NULL) + return (FALSE); + if (bp == curbp) + return (splitwind(f, n)); + /* and put it in a new, non-ephemeral window */ + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + curbp = bp; + curwp = wp; + return (TRUE); +} + +/* + * Dispose of a buffer, by name. + * Ask for the name. Look it up (don't get too + * upset if it isn't there at all!). Clear the buffer (ask + * if the buffer has been changed). Then free the header + * line and the buffer header. Bound to "C-X k". + */ +/* ARGSUSED */ +int +killbuffer_cmd(int f, int n) +{ + struct buffer *bp; + char bufn[NBUFN], *bufp; + + if ((bufp = eread("Kill buffer: (default %s) ", bufn, NBUFN, + EFNUL | EFNEW | EFBUF, curbp->b_bname)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + bp = curbp; + else if ((bp = bfind(bufn, FALSE)) == NULL) + return (FALSE); + return (killbuffer(bp)); +} + +int +killbuffer(struct buffer *bp) +{ + struct buffer *bp1; + struct buffer *bp2; + struct mgwin *wp; + int s; + struct undo_rec *rec, *next; + + /* + * Find some other buffer to display. Try the alternate buffer, + * then the first different buffer in the buffer list. If there's + * only one buffer, create buffer *scratch* and make it the alternate + * buffer. Return if *scratch* is only buffer... + */ + if ((bp1 = bp->b_altb) == NULL) { + bp1 = (bp == bheadp) ? bp->b_bufp : bheadp; + if (bp1 == NULL) { + /* only one buffer. see if it's *scratch* */ + if (bp == bfind("*scratch*", FALSE)) + return (TRUE); + /* create *scratch* for alternate buffer */ + if ((bp1 = bfind("*scratch*", TRUE)) == NULL) + return (FALSE); + } + } + if ((s = bclear(bp)) != TRUE) + return (s); + for (wp = wheadp; bp->b_nwnd > 0; wp = wp->w_wndp) { + if (wp->w_bufp == bp) { + bp2 = bp1->b_altb; /* save alternate buffer */ + if (showbuffer(bp1, wp, WFMODE | WFFRAME | WFFULL)) + bp1->b_altb = bp2; + else + bp1 = bp2; + } + } + if (bp == curbp) + curbp = bp1; + free(bp->b_headp); /* Release header line. */ + bp2 = NULL; /* Find the header. */ + bp1 = bheadp; + while (bp1 != bp) { + if (bp1->b_altb == bp) + bp1->b_altb = (bp->b_altb == bp1) ? NULL : bp->b_altb; + bp2 = bp1; + bp1 = bp1->b_bufp; + } + bp1 = bp1->b_bufp; /* Next one in chain. */ + if (bp2 == NULL) /* Unlink it. */ + bheadp = bp1; + else + bp2->b_bufp = bp1; + while (bp1 != NULL) { /* Finish with altb's */ + if (bp1->b_altb == bp) + bp1->b_altb = (bp->b_altb == bp1) ? NULL : bp->b_altb; + bp1 = bp1->b_bufp; + } + rec = TAILQ_FIRST(&bp->b_undo); + + while (rec != NULL) { + next = TAILQ_NEXT(rec, next); + free_undo_record(rec); + rec = next; + } + + free(bp->b_bname); /* Release name block */ + free(bp); /* Release buffer block */ + return (TRUE); +} + +/* + * Save some buffers - just call anycb with the arg flag. + */ +/* ARGSUSED */ +int +savebuffers(int f, int n) +{ + if (anycb(f) == ABORT) + return (ABORT); + return (TRUE); +} + +/* + * Listing buffers. + */ +static int listbuf_ncol; + +static int listbuf_goto_buffer(int f, int n); +static int listbuf_goto_buffer_one(int f, int n); +static int listbuf_goto_buffer_helper(int f, int n, int only); + +static PF listbuf_pf[] = { + listbuf_goto_buffer +}; +static PF listbuf_one[] = { + listbuf_goto_buffer_one +}; + + +static struct KEYMAPE (2 + IMAPEXT) listbufmap = { + 2, + 2 + IMAPEXT, + rescan, + { + { + CCHR('M'), CCHR('M'), listbuf_pf, NULL + }, + { + '1', '1', listbuf_one, NULL + } + } +}; + +/* + * Display the buffer list. This is done + * in two parts. The "makelist" routine figures out + * the text, and puts it in a buffer. "popbuf" + * then pops the data onto the screen. Bound to + * "C-X C-B". + */ +/* ARGSUSED */ +int +listbuffers(int f, int n) +{ + static int initialized = 0; + struct buffer *bp; + struct mgwin *wp; + + if (!initialized) { + maps_add((KEYMAP *)&listbufmap, "listbufmap"); + initialized = 1; + } + + if ((bp = makelist()) == NULL || (wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + wp->w_dotp = bp->b_dotp; /* fix up if window already on screen */ + wp->w_doto = bp->b_doto; + bp->b_modes[0] = name_mode("fundamental"); + bp->b_modes[1] = name_mode("listbufmap"); + bp->b_nmodes = 1; + + return (TRUE); +} + +/* + * This routine rebuilds the text for the + * list buffers command. Return pointer + * to new list if everything works. + * Return NULL if there is an error (if + * there is no memory). + */ +static struct buffer * +makelist(void) +{ + int w = ncol / 2; + struct buffer *bp, *blp; + struct line *lp; + + if ((blp = bfind("*Buffer List*", TRUE)) == NULL) + return (NULL); + if (bclear(blp) != TRUE) + return (NULL); + blp->b_flag &= ~BFCHG; /* Blow away old. */ + blp->b_flag |= BFREADONLY; + + listbuf_ncol = ncol; /* cache ncol for listbuf_goto_buffer */ + + if (addlinef(blp, "%-*s%s", w, " MR Buffer", "Size File") == FALSE || + addlinef(blp, "%-*s%s", w, " -- ------", "---- ----") == FALSE) + return (NULL); + + for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { + RSIZE nbytes; + + nbytes = 0; /* Count bytes in buf. */ + if (bp != blp) { + lp = bfirstlp(bp); + while (lp != bp->b_headp) { + nbytes += llength(lp) + 1; + lp = lforw(lp); + } + if (nbytes) + nbytes--; /* no bonus newline */ + } + + if (addlinef(blp, "%c%c%c %-*.*s%c%-6d %-*s", + (bp == curbp) ? '.' : ' ', /* current buffer ? */ + ((bp->b_flag & BFCHG) != 0) ? '*' : ' ', /* changed ? */ + ((bp->b_flag & BFREADONLY) != 0) ? ' ' : '*', + w - 5, /* four chars already written */ + w - 5, /* four chars already written */ + bp->b_bname, /* buffer name */ + strlen(bp->b_bname) < w - 5 ? ' ' : '$', /* truncated? */ + nbytes, /* buffer size */ + w - 7, /* seven chars already written */ + bp->b_fname) == FALSE) + return (NULL); + } + blp->b_dotp = bfirstlp(blp); /* put dot at beginning of + * buffer */ + blp->b_doto = 0; + return (blp); /* All done */ +} + +static int +listbuf_goto_buffer(int f, int n) +{ + return (listbuf_goto_buffer_helper(f, n, 0)); +} + +static int +listbuf_goto_buffer_one(int f, int n) +{ + return (listbuf_goto_buffer_helper(f, n, 1)); +} + +static int +listbuf_goto_buffer_helper(int f, int n, int only) +{ + struct buffer *bp; + struct mgwin *wp; + char *line = NULL; + int i, ret = FALSE; + + if (curwp->w_dotp->l_text[listbuf_ncol/2 - 1] == '$') { + ewprintf("buffer name truncated"); + return (FALSE); + } + + if ((line = malloc(listbuf_ncol/2)) == NULL) + return (FALSE); + + memcpy(line, curwp->w_dotp->l_text + 4, listbuf_ncol/2 - 5); + for (i = listbuf_ncol/2 - 6; i > 0; i--) { + if (line[i] != ' ') { + line[i + 1] = '\0'; + break; + } + } + if (i == 0) + goto cleanup; + + for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { + if (strcmp(bp->b_bname, line) == 0) + break; + } + if (bp == NULL) + goto cleanup; + + if ((wp = popbuf(bp, WNONE)) == NULL) + goto cleanup; + curbp = bp; + curwp = wp; + + if (only) + ret = (onlywind(f, n)); + else + ret = TRUE; + +cleanup: + free(line); + + return (ret); +} + +/* + * The argument "fmt" points to a format string. Append this line to the + * buffer. Handcraft the EOL on the end. Return TRUE if it worked and + * FALSE if you ran out of room. + */ +int +addlinef(struct buffer *bp, char *fmt, ...) +{ + va_list ap; + struct line *lp; + + if ((lp = lalloc(0)) == NULL) + return (FALSE); + va_start(ap, fmt); + if (vasprintf(&lp->l_text, fmt, ap) == -1) { + lfree(lp); + va_end(ap); + return (FALSE); + } + lp->l_used = strlen(lp->l_text); + va_end(ap); + + bp->b_headp->l_bp->l_fp = lp; /* Hook onto the end */ + lp->l_bp = bp->b_headp->l_bp; + bp->b_headp->l_bp = lp; + lp->l_fp = bp->b_headp; + bp->b_lines++; + + return (TRUE); +} + +/* + * Look through the list of buffers, giving the user a chance to save them. + * Return TRUE if there are any changed buffers afterwards. Buffers that don't + * have an associated file don't count. Return FALSE if there are no changed + * buffers. Return ABORT if an error occurs or if the user presses c-g. + */ +int +anycb(int f) +{ + struct buffer *bp; + int s = FALSE, save = FALSE, ret; + char pbuf[NFILEN + 11]; + + for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { + if (bp->b_fname != NULL && *(bp->b_fname) != '\0' && + (bp->b_flag & BFCHG) != 0) { + ret = snprintf(pbuf, sizeof(pbuf), "Save file %s", + bp->b_fname); + if (ret < 0 || ret >= sizeof(pbuf)) { + ewprintf("Error: filename too long!"); + return (ABORT); + } + if ((f == TRUE || (save = eyorn(pbuf)) == TRUE) && + buffsave(bp) == TRUE) { + bp->b_flag &= ~BFCHG; + upmodes(bp); + } else + s = TRUE; + if (save == ABORT) + return (save); + save = TRUE; + } + } + if (save == FALSE /* && kbdmop == NULL */ ) /* experimental */ + ewprintf("(No files need saving)"); + return (s); +} + +/* + * Search for a buffer, by name. + * If not found, and the "cflag" is TRUE, + * create a new buffer. Return pointer to the found + * (or new) buffer. + */ +struct buffer * +bfind(const char *bname, int cflag) +{ + struct buffer *bp; + + bp = bheadp; + while (bp != NULL) { + if (strcmp(bname, bp->b_bname) == 0) + return (bp); + bp = bp->b_bufp; + } + if (cflag != TRUE) + return (NULL); + + bp = bnew(bname); + + return (bp); +} + +/* + * Create a new buffer and put it in the list of + * all buffers. + */ +static struct buffer * +bnew(const char *bname) +{ + struct buffer *bp; + struct line *lp; + int i; + size_t len; + + bp = calloc(1, sizeof(struct buffer)); + if (bp == NULL) { + ewprintf("Can't get %d bytes", sizeof(struct buffer)); + return (NULL); + } + if ((lp = lalloc(0)) == NULL) { + free(bp); + return (NULL); + } + bp->b_altb = bp->b_bufp = NULL; + bp->b_dotp = lp; + bp->b_doto = 0; + bp->b_markp = NULL; + bp->b_marko = 0; + bp->b_flag = defb_flag; + /* if buffer name starts and ends with '*', we ignore changes */ + len = strlen(bname); + if (len) { + if (bname[0] == '*' && bname[len - 1] == '*') + bp->b_flag |= BFIGNDIRTY; + } + bp->b_nwnd = 0; + bp->b_headp = lp; + bp->b_nmodes = defb_nmodes; + TAILQ_INIT(&bp->b_undo); + bp->b_undoptr = NULL; + memset(&bp->b_undopos, 0, sizeof(bp->b_undopos)); + i = 0; + do { + bp->b_modes[i] = defb_modes[i]; + } while (i++ < defb_nmodes); + bp->b_fname[0] = '\0'; + bp->b_cwd[0] = '\0'; + bzero(&bp->b_fi, sizeof(bp->b_fi)); + lp->l_fp = lp; + lp->l_bp = lp; + bp->b_bufp = bheadp; + bheadp = bp; + bp->b_dotline = bp->b_markline = 1; + bp->b_lines = 1; + if ((bp->b_bname = strdup(bname)) == NULL) { + ewprintf("Can't get %d bytes", strlen(bname) + 1); + return (NULL); + } + + return (bp); +} + +/* + * This routine blows away all of the text + * in a buffer. If the buffer is marked as changed + * then we ask if it is ok to blow it away; this is + * to save the user the grief of losing text. The + * window chain is nearly always wrong if this gets + * called; the caller must arrange for the updates + * that are required. Return TRUE if everything + * looks good. + */ +int +bclear(struct buffer *bp) +{ + struct line *lp; + int s; + + /* Has buffer changed, and do we care? */ + if (!(bp->b_flag & BFIGNDIRTY) && (bp->b_flag & BFCHG) != 0 && + (s = eyesno("Buffer modified; kill anyway")) != TRUE) + return (s); + bp->b_flag &= ~BFCHG; /* Not changed */ + while ((lp = lforw(bp->b_headp)) != bp->b_headp) + lfree(lp); + bp->b_dotp = bp->b_headp; /* Fix dot */ + bp->b_doto = 0; + bp->b_markp = NULL; /* Invalidate "mark" */ + bp->b_marko = 0; + bp->b_dotline = bp->b_markline = 1; + bp->b_lines = 1; + + return (TRUE); +} + +/* + * Display the given buffer in the given window. Flags indicated + * action on redisplay. Update modified flag so insert loop can check it. + */ +int +showbuffer(struct buffer *bp, struct mgwin *wp, int flags) +{ + struct buffer *obp; + struct mgwin *owp; + + /* Ensure file has not been modified elsewhere */ + if (fchecktime(bp) != TRUE) + bp->b_flag |= BFDIRTY; + + if (wp->w_bufp == bp) { /* Easy case! */ + wp->w_rflag |= flags; + wp->w_dotp = bp->b_dotp; + wp->w_doto = bp->b_doto; + return (TRUE); + } + /* First, detach the old buffer from the window */ + if ((bp->b_altb = obp = wp->w_bufp) != NULL) { + if (--obp->b_nwnd == 0) { + obp->b_dotp = wp->w_dotp; + obp->b_doto = wp->w_doto; + obp->b_markp = wp->w_markp; + obp->b_marko = wp->w_marko; + obp->b_dotline = wp->w_dotline; + obp->b_markline = wp->w_markline; + } + } + /* Now, attach the new buffer to the window */ + wp->w_bufp = bp; + + if (bp->b_nwnd++ == 0) { /* First use. */ + wp->w_dotp = bp->b_dotp; + wp->w_doto = bp->b_doto; + wp->w_markp = bp->b_markp; + wp->w_marko = bp->b_marko; + wp->w_dotline = bp->b_dotline; + wp->w_markline = bp->b_markline; + } else + /* already on screen, steal values from other window */ + for (owp = wheadp; owp != NULL; owp = wp->w_wndp) + if (wp->w_bufp == bp && owp != wp) { + wp->w_dotp = owp->w_dotp; + wp->w_doto = owp->w_doto; + wp->w_markp = owp->w_markp; + wp->w_marko = owp->w_marko; + wp->w_dotline = owp->w_dotline; + wp->w_markline = owp->w_markline; + break; + } + wp->w_rflag |= WFMODE | flags; + return (TRUE); +} + +/* + * Augment a buffer name with a number, if necessary + * + * If more than one file of the same basename() is open, + * the additional buffers are named "file<2>", "file<3>", and + * so forth. This function adjusts a buffer name to + * include the number, if necessary. + */ +int +augbname(char *bn, const char *fn, size_t bs) +{ + int count; + size_t remain, len; + + if ((len = xbasename(bn, fn, bs)) >= bs) + return (FALSE); + + remain = bs - len; + for (count = 2; bfind(bn, FALSE) != NULL; count++) + snprintf(bn + len, remain, "<%d>", count); + + return (TRUE); +} + +/* + * Pop the buffer we got passed onto the screen. + * Returns a status. + */ +struct mgwin * +popbuf(struct buffer *bp, int flags) +{ + struct mgwin *wp; + + if (bp->b_nwnd == 0) { /* Not on screen yet. */ + /* + * Pick a window for a pop-up. + * If only one window, split the screen. + * Flag the new window as ephemeral + */ + if (wheadp->w_wndp == NULL && + splitwind(FFOTHARG, flags) == FALSE) + return (NULL); + + /* + * Pick the uppermost window that isn't + * the current window. An LRU algorithm + * might be better. Return a pointer, or NULL on error. + */ + wp = wheadp; + + while (wp != NULL && wp == curwp) + wp = wp->w_wndp; + } else + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) + if (wp->w_bufp == bp) { + wp->w_rflag |= WFFULL | WFFRAME; + return (wp); + } + if (showbuffer(bp, wp, WFFULL) != TRUE) + return (NULL); + return (wp); +} + +/* + * Insert another buffer at dot. Very useful. + */ +/* ARGSUSED */ +int +bufferinsert(int f, int n) +{ + struct buffer *bp; + struct line *clp; + int clo, nline; + char bufn[NBUFN], *bufp; + + /* Get buffer to use from user */ + if (curbp->b_altb != NULL) + bufp = eread("Insert buffer: (default %s) ", bufn, NBUFN, + EFNUL | EFNEW | EFBUF, curbp->b_altb->b_bname); + else + bufp = eread("Insert buffer: ", bufn, NBUFN, EFNEW | EFBUF); + if (bufp == NULL) + return (ABORT); + if (bufp[0] == '\0' && curbp->b_altb != NULL) + bp = curbp->b_altb; + else if ((bp = bfind(bufn, FALSE)) == NULL) + return (FALSE); + + if (bp == curbp) { + ewprintf("Cannot insert buffer into self"); + return (FALSE); + } + /* insert the buffer */ + nline = 0; + clp = bfirstlp(bp); + for (;;) { + for (clo = 0; clo < llength(clp); clo++) + if (linsert(1, lgetc(clp, clo)) == FALSE) + return (FALSE); + if ((clp = lforw(clp)) == bp->b_headp) + break; + if (newline(FFRAND, 1) == FALSE) /* fake newline */ + return (FALSE); + nline++; + } + if (nline == 1) + ewprintf("[Inserted 1 line]"); + else + ewprintf("[Inserted %d lines]", nline); + + clp = curwp->w_linep; /* cosmetic adjustment */ + if (curwp->w_dotp == clp) { /* for offscreen insert */ + while (nline-- && lback(clp) != curbp->b_headp) + clp = lback(clp); + curwp->w_linep = clp; /* adjust framing. */ + curwp->w_rflag |= WFFULL; + } + return (TRUE); +} + +/* + * Turn off the dirty bit on this buffer. + */ +/* ARGSUSED */ +int +notmodified(int f, int n) +{ + struct mgwin *wp; + + curbp->b_flag &= ~BFCHG; + wp = wheadp; /* Update mode lines. */ + while (wp != NULL) { + if (wp->w_bufp == curbp) + wp->w_rflag |= WFMODE; + wp = wp->w_wndp; + } + ewprintf("Modification-flag cleared"); + return (TRUE); +} + +/* + * Popbuf and set all windows to top of buffer. + */ +int +popbuftop(struct buffer *bp, int flags) +{ + struct mgwin *wp; + + bp->b_dotp = bfirstlp(bp); + bp->b_doto = 0; + if (bp->b_nwnd != 0) { + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) + if (wp->w_bufp == bp) { + wp->w_dotp = bp->b_dotp; + wp->w_doto = 0; + wp->w_rflag |= WFFULL; + } + } + return (popbuf(bp, flags) != NULL); +} + +/* + * Return the working directory for the current buffer, terminated + * with a '/'. First, try to extract it from the current buffer's + * filename. If that fails, use global cwd. + */ +int +getbufcwd(char *path, size_t plen) +{ + char cwd[NFILEN]; + + if (plen == 0) + return (FALSE); + + if (globalwd == FALSE && curbp->b_cwd[0] != '\0') { + (void)strlcpy(path, curbp->b_cwd, plen); + } else { + if (getcwdir(cwd, sizeof(cwd)) == FALSE) + goto error; + (void)strlcpy(path, cwd, plen); + } + return (TRUE); +error: + path[0] = '\0'; + return (FALSE); +} + +/* + * Ensures a buffer has not been modified elsewhere; e.g. on disk. + * Prompt the user if it has. + * Returns TRUE if it has NOT (i.e. buffer is ok to edit). + * FALSE or ABORT otherwise + */ +int +checkdirty(struct buffer *bp) +{ + int s; + + if ((bp->b_flag & (BFDIRTY | BFIGNDIRTY)) == BFDIRTY) { + if ((s = eyorn("File changed on disk; really edit the buffer")) + != TRUE) + return (s); + bp->b_flag &= ~BFDIRTY; + bp->b_flag |= BFIGNDIRTY; + } + + return (TRUE); +} + diff --git a/mg/chrdef.h b/mg/chrdef.h new file mode 100644 index 0000000..ed94098 --- /dev/null +++ b/mg/chrdef.h @@ -0,0 +1,81 @@ +/* $OpenBSD: chrdef.h,v 1.7 2005/06/14 18:14:40 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * sys/default/chardef.h: character set specific #defines for Mg 2a + * Warning: System specific ones exist + */ + +/* + * Casting should be at least as efficient as anding with 0xff, + * and won't have the size problems. Override in sysdef.h if no + * unsigned char type. + */ +#define CHARMASK(c) ((unsigned char) (c)) + +/* + * These flags, and the macros below them, + * make up a do-it-yourself set of "ctype" macros that + * understand the DEC multinational set, and let me ask + * a slightly different set of questions. + */ +#define _MG_W 0x01 /* Word. */ +#define _MG_U 0x02 /* Upper case letter. */ +#define _MG_L 0x04 /* Lower case letter. */ +#define _MG_C 0x08 /* Control. */ +#define _MG_P 0x10 /* end of sentence punctuation */ +#define _MG_D 0x20 /* is decimal digit */ + +#define ISWORD(c) ((cinfo[CHARMASK(c)]&_MG_W)!=0) +#define ISCTRL(c) ((cinfo[CHARMASK(c)]&_MG_C)!=0) +#define ISUPPER(c) ((cinfo[CHARMASK(c)]&_MG_U)!=0) +#define ISLOWER(c) ((cinfo[CHARMASK(c)]&_MG_L)!=0) +#define ISEOSP(c) ((cinfo[CHARMASK(c)]&_MG_P)!=0) +#define ISDIGIT(c) ((cinfo[CHARMASK(c)]&_MG_D)!=0) +#define TOUPPER(c) ((c)-0x20) +#define TOLOWER(c) ((c)+0x20) + +/* + * Generally useful thing for chars + */ +#define CCHR(x) ((x) ^ 0x40) /* CCHR('?') == DEL */ + +#ifndef METACH +#define METACH CCHR('[') +#endif + +#ifdef XKEYS +#define K00 256 +#define K01 257 +#define K02 258 +#define K03 259 +#define K04 260 +#define K05 261 +#define K06 262 +#define K07 263 +#define K08 264 +#define K09 265 +#define K0A 266 +#define K0B 267 +#define K0C 268 +#define K0D 269 +#define K0E 270 +#define K0F 271 +#define K10 272 +#define K11 273 +#define K12 274 +#define K13 275 +#define K14 276 +#define K15 277 +#define K16 278 +#define K17 279 +#define K18 280 +#define K19 281 +#define K1A 282 +#define K1B 283 +#define K1C 284 +#define K1D 285 +#define K1E 286 +#define K1F 287 +#endif diff --git a/mg/cinfo.c b/mg/cinfo.c new file mode 100644 index 0000000..f568897 --- /dev/null +++ b/mg/cinfo.c @@ -0,0 +1,154 @@ +/* $OpenBSD: cinfo.c,v 1.16 2011/11/28 04:41:39 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Character class tables. + * Do it yourself character classification + * macros, that understand the multinational character set, + * and let me ask some questions the standard macros (in + * ctype.h) don't let you ask. + */ +#include "def.h" + +/* + * This table, indexed by a character drawn + * from the 256 member character set, is used by my + * own character type macros to answer questions about the + * type of a character. It handles the full multinational + * character set, and lets me ask some questions that the + * standard "ctype" macros cannot ask. + */ +/* + * Due to incompatible behaviour between "standard" emacs and + * ctags word traversing, '_' character's value is changed on + * the fly in ctags mode, hence non-const. + */ +char cinfo[256] = { + _MG_C, _MG_C, _MG_C, _MG_C, /* 0x0X */ + _MG_C, _MG_C, _MG_C, _MG_C, + _MG_C, _MG_C, _MG_C, _MG_C, + _MG_C, _MG_C, _MG_C, _MG_C, + _MG_C, _MG_C, _MG_C, _MG_C, /* 0x1X */ + _MG_C, _MG_C, _MG_C, _MG_C, + _MG_C, _MG_C, _MG_C, _MG_C, + _MG_C, _MG_C, _MG_C, _MG_C, + 0, _MG_P, 0, 0, /* 0x2X */ + _MG_W, _MG_W, 0, _MG_W, + 0, 0, 0, 0, + 0, 0, _MG_P, 0, + _MG_D | _MG_W, _MG_D | _MG_W, _MG_D | _MG_W, _MG_D | _MG_W, /* 0x3X */ + _MG_D | _MG_W, _MG_D | _MG_W, _MG_D | _MG_W, _MG_D | _MG_W, + _MG_D | _MG_W, _MG_D | _MG_W, 0, 0, + 0, 0, 0, _MG_P, + 0, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, /* 0x4X */ + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, /* 0x5X */ + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, 0, + 0, 0, 0, 0, + 0, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, /* 0x6X */ + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, /* 0x7X */ + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, 0, + 0, 0, 0, _MG_C, + 0, 0, 0, 0, /* 0x8X */ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, /* 0x9X */ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, /* 0xAX */ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, /* 0xBX */ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, /* 0xCX */ + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, + 0, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, /* 0xDX */ + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, + _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, + _MG_U | _MG_W, _MG_U | _MG_W, 0, _MG_W, + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, /* 0xEX */ + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, + 0, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, /* 0xFX */ + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, + _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, + _MG_L | _MG_W, _MG_L | _MG_W, 0, 0 +}; + +/* + * Find the name of a keystroke. Needs to be changed to handle 8-bit printing + * characters and function keys better. Returns a pointer to the terminating + * '\0'. Returns NULL on failure. + */ +char * +getkeyname(char *cp, size_t len, int k) +{ + const char *np; + size_t copied; + + if (k < 0) + k = CHARMASK(k); /* sign extended char */ + switch (k) { + case CCHR('@'): + np = "C-SPC"; + break; + case CCHR('I'): + np = "TAB"; + break; + case CCHR('M'): + np = "RET"; + break; + case CCHR('['): + np = "ESC"; + break; + case ' ': + np = "SPC"; + break; /* yuck again */ + case CCHR('?'): + np = "DEL"; + break; + default: +#ifdef FKEYS + if (k >= KFIRST && k <= KLAST && + (np = keystrings[k - KFIRST]) != NULL) + break; +#endif + if (k > CCHR('?')) { + *cp++ = '0'; + *cp++ = ((k >> 6) & 7) + '0'; + *cp++ = ((k >> 3) & 7) + '0'; + *cp++ = (k & 7) + '0'; + *cp = '\0'; + return (cp); + } else if (k < ' ') { + *cp++ = 'C'; + *cp++ = '-'; + k = CCHR(k); + if (ISUPPER(k)) + k = TOLOWER(k); + } + *cp++ = k; + *cp = '\0'; + return (cp); + } + copied = strlcpy(cp, np, len); + if (copied >= len) + copied = len - 1; + return (cp + copied); +} diff --git a/mg/cmode.c b/mg/cmode.c new file mode 100644 index 0000000..674d89d --- /dev/null +++ b/mg/cmode.c @@ -0,0 +1,528 @@ +/* $OpenBSD: cmode.c,v 1.8 2012/05/18 02:13:44 lum Exp $ */ +/* + * This file is in the public domain. + * + * Author: Kjell Wooding + */ + +/* + * Implement an non-irritating KNF-compliant mode for editing + * C code. + */ +#include + +#include "def.h" +#include "kbd.h" +#include "funmap.h" + +/* Pull in from modes.c */ +extern int changemode(int, int, char *); + +static int cc_strip_trailp = TRUE; /* Delete Trailing space? */ +static int cc_basic_indent = 8; /* Basic Indent multiple */ +static int cc_cont_indent = 4; /* Continued line indent */ +static int cc_colon_indent = -8; /* Label / case indent */ + +static int getmatch(int, int); +static int getindent(const struct line *, int *); +static int in_whitespace(struct line *, int); +static int findcolpos(const struct buffer *, const struct line *, int); +static struct line *findnonblank(struct line *); +static int isnonblank(const struct line *, int); + +void cmode_init(void); +int cc_comment(int, int); + +/* Keymaps */ + +static PF cmode_brace[] = { + cc_brace, /* } */ +}; + +static PF cmode_cCP[] = { + compile, /* C-c P */ +}; + + +static PF cmode_cc[] = { + NULL, /* ^C */ + rescan, /* ^D */ + rescan, /* ^E */ + rescan, /* ^F */ + rescan, /* ^G */ + rescan, /* ^H */ + cc_tab, /* ^I */ + rescan, /* ^J */ + rescan, /* ^K */ + rescan, /* ^L */ + cc_lfindent, /* ^M */ +}; + +static PF cmode_spec[] = { + cc_char, /* : */ +}; + +static struct KEYMAPE (1 + IMAPEXT) cmode_cmap = { + 1, + 1 + IMAPEXT, + rescan, + { + { 'P', 'P', cmode_cCP, NULL } + } +}; + +static struct KEYMAPE (3 + IMAPEXT) cmodemap = { + 3, + 3 + IMAPEXT, + rescan, + { + { CCHR('C'), CCHR('M'), cmode_cc, (KEYMAP *) &cmode_cmap }, + { ':', ':', cmode_spec, NULL }, + { '}', '}', cmode_brace, NULL } + } +}; + +/* Funtion, Mode hooks */ + +void +cmode_init(void) +{ + funmap_add(cmode, "c-mode"); + funmap_add(cc_char, "c-handle-special-char"); + funmap_add(cc_brace, "c-handle-special-brace"); + funmap_add(cc_tab, "c-tab-or-indent"); + funmap_add(cc_indent, "c-indent"); + funmap_add(cc_lfindent, "c-indent-and-newline"); + maps_add((KEYMAP *)&cmodemap, "c"); +} + +/* + * Enable/toggle c-mode + */ +int +cmode(int f, int n) +{ + return(changemode(f, n, "c")); +} + +/* + * Handle special C character - selfinsert then indent. + */ +int +cc_char(int f, int n) +{ + if (n < 0) + return (FALSE); + if (selfinsert(FFRAND, n) == FALSE) + return (FALSE); + return (cc_indent(FFRAND, n)); +} + +/* + * Handle special C character - selfinsert then indent. + */ +int +cc_brace(int f, int n) +{ + if (n < 0) + return (FALSE); + if (showmatch(FFRAND, 1) == FALSE) + return (FALSE); + return (cc_indent(FFRAND, n)); +} + + +/* + * If we are in the whitespace at the beginning of the line, + * simply act as a regular tab. If we are not, indent + * current line according to whitespace rules. + */ +int +cc_tab(int f, int n) +{ + int inwhitep = FALSE; /* In leading whitespace? */ + + inwhitep = in_whitespace(curwp->w_dotp, llength(curwp->w_dotp)); + + /* If empty line, or in whitespace */ + if (llength(curwp->w_dotp) == 0 || inwhitep) + return (selfinsert(f, n)); + + return (cc_indent(FFRAND, 1)); +} + +/* + * Attempt to indent current line according to KNF rules. + */ +int +cc_indent(int f, int n) +{ + int pi, mi; /* Previous indents */ + int ci, dci; /* current indent, don't care */ + struct line *lp; + int ret; + + if (n < 0) + return (FALSE); + + undo_boundary_enable(FFRAND, 0); + if (cc_strip_trailp) + deltrailwhite(FFRAND, 1); + + /* + * Search backwards for a non-blank, non-preprocessor, + * non-comment line + */ + + lp = findnonblank(curwp->w_dotp); + + pi = getindent(lp, &mi); + + /* Strip leading space on current line */ + delleadwhite(FFRAND, 1); + /* current indent is computed only to current position */ + dci = getindent(curwp->w_dotp, &ci); + + if (pi + ci < 0) + ret = indent(FFOTHARG, 0); + else + ret = indent(FFOTHARG, pi + ci); + + undo_boundary_enable(FFRAND, 1); + + return (ret); +} + +/* + * Indent-and-newline (technically, newline then indent) + */ +int +cc_lfindent(int f, int n) +{ + if (n < 0) + return (FALSE); + if (newline(FFRAND, 1) == FALSE) + return (FALSE); + return (cc_indent(FFRAND, n)); +} + +/* + * Get the level of indention after line lp is processed + * Note getindent has two returns: + * curi = value if indenting current line. + * return value = value affecting subsequent lines. + */ +static int +getindent(const struct line *lp, int *curi) +{ + int lo, co; /* leading space, current offset*/ + int nicol = 0; /* position count */ + int ccol = 0; /* current column */ + int c = '\0'; /* current char */ + int newind = 0; /* new index value */ + int stringp = FALSE; /* in string? */ + int escp = FALSE; /* Escape char? */ + int lastc = '\0'; /* Last matched string delimeter */ + int nparen = 0; /* paren count */ + int obrace = 0; /* open brace count */ + int cbrace = 0; /* close brace count */ + int contp = FALSE; /* Continue? */ + int firstnwsp = FALSE; /* First nonspace encountered? */ + int colonp = FALSE; /* Did we see a colon? */ + int questionp = FALSE; /* Did we see a question mark? */ + int slashp = FALSE; /* Slash? */ + int astp = FALSE; /* Asterisk? */ + int cpos = -1; /* comment position */ + int cppp = FALSE; /* Preprocessor command? */ + + *curi = 0; + + /* Compute leading space */ + for (lo = 0; lo < llength(lp); lo++) { + if (!isspace(c = lgetc(lp, lo))) + break; + if (c == '\t' +#ifdef NOTAB + && !(curbp->b_flag & BFNOTAB) +#endif /* NOTAB */ + ) { + nicol |= 0x07; + } + nicol++; + } + + /* If last line was blank, choose 0 */ + if (lo == llength(lp)) + nicol = 0; + + newind = 0; + ccol = nicol; /* current column */ + /* Compute modifiers */ + for (co = lo; co < llength(lp); co++) { + c = lgetc(lp, co); + /* We have a non-whitespace char */ + if (!firstnwsp && !isspace(c)) { + contp = TRUE; + if (c == '#') + cppp = TRUE; + firstnwsp = TRUE; + } + if (c == '\\') + escp = !escp; + else if (stringp) { + if (!escp && (c == '"' || c == '\'')) { + /* unescaped string char */ + if (getmatch(c, lastc)) + stringp = FALSE; + } + } else if (c == '"' || c == '\'') { + stringp = TRUE; + lastc = c; + } else if (c == '(') { + nparen++; + } else if (c == ')') { + nparen--; + } else if (c == '{') { + obrace++; + firstnwsp = FALSE; + contp = FALSE; + } else if (c == '}') { + cbrace++; + } else if (c == '?') { + questionp = TRUE; + } else if (c == ':') { + /* ignore (foo ? bar : baz) construct */ + if (!questionp) + colonp = TRUE; + } else if (c == ';') { + if (nparen > 0) + contp = FALSE; + } else if (c == '/') { + /* first nonwhitespace? -> indent */ + if (firstnwsp) { + /* If previous char asterisk -> close */ + if (astp) + cpos = -1; + else + slashp = TRUE; + } + } else if (c == '*') { + /* If previous char slash -> open */ + if (slashp) + cpos = co; + else + astp = TRUE; + } else if (firstnwsp) { + firstnwsp = FALSE; + } + + /* Reset matches that apply to next character only */ + if (c != '\\') + escp = FALSE; + if (c != '*') + astp = FALSE; + if (c != '/') + slashp = FALSE; + } + /* + * If not terminated with a semicolon, and brace or paren open. + * we continue + */ + if (colonp) { + *curi += cc_colon_indent; + newind -= cc_colon_indent; + } + + *curi -= (cbrace) * cc_basic_indent; + newind += obrace * cc_basic_indent; + + if (nparen < 0) + newind -= cc_cont_indent; + else if (nparen > 0) + newind += cc_cont_indent; + + *curi += nicol; + + /* Ignore preprocessor. Otherwise, add current column */ + if (cppp) { + newind = nicol; + *curi = 0; + } else { + newind += nicol; + } + + if (cpos != -1) + newind = findcolpos(curbp, lp, cpos); + + return (newind); +} + +/* + * Given a delimeter and its purported mate, tell us if they + * match. + */ +static int +getmatch(int c, int mc) +{ + int match = FALSE; + + switch (c) { + case '"': + match = (mc == '"'); + break; + case '\'': + match = (mc == '\''); + break; + case '(': + match = (mc == ')'); + break; + case '[': + match = (mc == ']'); + break; + case '{': + match = (mc == '}'); + break; + } + + return (match); +} + +static int +in_whitespace(struct line *lp, int len) +{ + int lo; + int inwhitep = FALSE; + + for (lo = 0; lo < len; lo++) { + if (!isspace(lgetc(lp, lo))) + break; + if (lo == len - 1) + inwhitep = TRUE; + } + + return (inwhitep); +} + + +/* convert a line/offset pair to a column position (for indenting) */ +static int +findcolpos(const struct buffer *bp, const struct line *lp, int lo) +{ + int col, i, c; + char tmp[5]; + + /* determine column */ + col = 0; + + for (i = 0; i < lo; ++i) { + c = lgetc(lp, i); + if (c == '\t' +#ifdef NOTAB + && !(bp->b_flag & BFNOTAB) +#endif /* NOTAB */ + ) { + col |= 0x07; + col++; + } else if (ISCTRL(c) != FALSE) + col += 2; + else if (isprint(c)) { + col++; + } else { + col += snprintf(tmp, sizeof(tmp), "\\%o", c); + } + + } + return (col); +} + +/* + * Find a non-blank line, searching backwards from the supplied line pointer. + * For C, nonblank is non-preprocessor, non C++, and accounts + * for complete C-style comments. + */ +static struct line * +findnonblank(struct line *lp) +{ + int lo; + int nonblankp = FALSE; + int commentp = FALSE; + int slashp; + int astp; + int c; + + while (lback(lp) != curbp->b_headp && (commentp || !nonblankp)) { + lp = lback(lp); + slashp = FALSE; + astp = FALSE; + + /* Potential nonblank? */ + nonblankp = isnonblank(lp, llength(lp)); + + /* + * Search from end, removing complete C-style + * comments. If one is found, ignore it and + * test for nonblankness from where it starts. + */ + slashp = FALSE; + /* Scan backwards from end to find C-style comment */ + for (lo = llength(lp) - 1; lo >= 0; lo--) { + if (!isspace(c = lgetc(lp, lo))) { + if (commentp) { /* find comment "open" */ + if (c == '*') + astp = TRUE; + else if (astp && c == '/') { + commentp = FALSE; + /* whitespace to here? */ + nonblankp = isnonblank(lp, lo); + } + } else { /* find comment "close" */ + if (c == '/') + slashp = TRUE; + else if (slashp && c == '*') + /* found a comment */ + commentp = TRUE; + } + } + } + } + + /* Rewound to start of file? */ + if (lback(lp) == curbp->b_headp && !nonblankp) + return (curbp->b_headp); + + return (lp); +} + +/* + * Given a line, scan forward to 'omax' and determine if we + * are all C whitespace. + * Note that preprocessor directives and C++-style comments + * count as whitespace. C-style comments do not, and must + * be handled elsewhere. + */ +static int +isnonblank(const struct line *lp, int omax) +{ + int nonblankp = FALSE; /* Return value */ + int slashp = FALSE; /* Encountered slash */ + int lo; /* Loop index */ + int c; /* char being read */ + + /* Scan from front for preprocessor, C++ comments */ + for (lo = 0; lo < omax; lo++) { + if (!isspace(c = lgetc(lp, lo))) { + /* Possible nonblank line */ + nonblankp = TRUE; + /* skip // and # starts */ + if (c == '#' || (slashp && c == '/')) { + nonblankp = FALSE; + break; + } else if (!slashp && c == '/') { + slashp = TRUE; + continue; + } + } + slashp = FALSE; + } + return (nonblankp); +} diff --git a/mg/cscope.c b/mg/cscope.c new file mode 100644 index 0000000..9a8db5e --- /dev/null +++ b/mg/cscope.c @@ -0,0 +1,609 @@ +/* $OpenBSD: cscope.c,v 1.3 2012/07/02 08:08:31 lum Exp $ */ + +/* + * This file is in the public domain. + * + * Author: Sunil Nimmagadda + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "def.h" + +#define CSSYMBOL 0 +#define CSDEFINITION 1 +#define CSCALLEDFUNCS 2 +#define CSCALLERFUNCS 3 +#define CSTEXT 4 +#define CSEGREP 6 +#define CSFINDFILE 7 +#define CSINCLUDES 8 + +struct cstokens { + const char *fname; + const char *function; + const char *lineno; + const char *pattern; +}; + +struct csmatch { + TAILQ_ENTRY(csmatch) entry; + int lineno; +}; + +struct csrecord { + TAILQ_ENTRY(csrecord) entry; + char *filename; + TAILQ_HEAD(matches, csmatch) matches; +}; + +static TAILQ_HEAD(csrecords, csrecord) csrecords = TAILQ_HEAD_INITIALIZER(csrecords); +static struct csrecord *addentryr; +static struct csrecord *currecord; +static struct csmatch *curmatch; +static const char *addentryfn; +static const char *csprompt[] = { + "Find this symbol: ", + "Find this global definition: ", + "Find functions called by this function: ", + "Find functions calling this function: ", + "Find this text string: ", + "Change this text string: ", + "Find this egrep pattern: ", + "Find this file: ", + "Find files #including this file: " +}; + +static int addentry(struct buffer *, char *); +static void csflush(void); +static int do_cscope(int); +static int csexists(const char *); +static int getattr(char *, struct cstokens *); +static int jumptomatch(void); +static void prettyprint(struct buffer *, struct cstokens *); +static const char *ltrim(const char *); + +/* + * Find this symbol. Bound to C-c s s + */ +/* ARGSUSED */ +int +cssymbol(int f, int n) +{ + return (do_cscope(CSSYMBOL)); +} + +/* + * Find this global definition. Bound to C-c s d + */ +/* ARGSUSED */int +csdefinition(int f, int n) +{ + return (do_cscope(CSDEFINITION)); +} + +/* + * Find functions called by this function. Bound to C-c s l + */ +/* ARGSUSED */ +int +csfuncalled(int f, int n) +{ + return (do_cscope(CSCALLEDFUNCS)); +} + +/* + * Find functions calling this function. Bound to C-c s c + */ +/* ARGSUSED */ +int +cscallerfuncs(int f, int n) +{ + return (do_cscope(CSCALLERFUNCS)); +} + +/* + * Find this text. Bound to C-c s t + */ +/* ARGSUSED */ +int +csfindtext(int f, int n) +{ + return (do_cscope(CSTEXT)); +} + +/* + * Find this egrep pattern. Bound to C-c s e + */ +/* ARGSUSED */ +int +csegrep(int f, int n) +{ + return (do_cscope(CSEGREP)); +} + +/* + * Find this file. Bound to C-c s f + */ +/* ARGSUSED */ +int +csfindfile(int f, int n) +{ + return (do_cscope(CSFINDFILE)); +} + +/* + * Find files #including this file. Bound to C-c s i + */ +/* ARGSUSED */ +int +csfindinc(int f, int n) +{ + return (do_cscope(CSINCLUDES)); +} + +/* + * Create list of files to index in the given directory + * using cscope-indexer. + */ +/* ARGSUSED */ +int +cscreatelist(int f, int n) +{ + struct buffer *bp; + struct stat sb; + FILE *fpipe; + char dir[NFILEN], cmd[BUFSIZ], title[BUFSIZ], *line, *bufp; + size_t len; + int clen; + + if (getbufcwd(dir, sizeof(dir)) == FALSE) + dir[0] = '\0'; + + bufp = eread("Index files in directory: ", dir, + sizeof(dir), EFCR | EFDEF | EFNEW | EFNUL); + + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + + if (stat(dir, &sb) == -1) { + ewprintf("stat: %s", strerror(errno)); + return (FALSE); + } else if (S_ISDIR(sb.st_mode) == 0) { + ewprintf("%s: Not a directory", dir); + return (FALSE); + } + + if (csexists("cscope-indexer") == FALSE) { + ewprintf("no such file or directory, cscope-indexer"); + return (FALSE); + } + + clen = snprintf(cmd, sizeof(cmd), "cscope-indexer -v %s", dir); + if (clen < 0 || clen >= sizeof(cmd)) + return (FALSE); + + if ((fpipe = popen(cmd, "r")) == NULL) { + ewprintf("problem opening pipe"); + return (FALSE); + } + + bp = bfind("*cscope*", TRUE); + if (bclear(bp) != TRUE) + return (FALSE); + bp->b_flag |= BFREADONLY; + + clen = snprintf(title, sizeof(title), "%s%s", + "Creating cscope file list 'cscope.files' in: ", dir); + if (clen < 0 || clen >= sizeof(title)) + return (FALSE); + addline(bp, title); + addline(bp, ""); + /* All lines are NUL terminated */ + while ((line = fgetln(fpipe, &len)) != NULL) { + line[len - 1] = '\0'; + addline(bp, line); + } + pclose(fpipe); + return (popbuftop(bp, WNONE)); +} + +/* + * Next Symbol. Bound to C-c s n + */ +/* ARGSUSED */ +int +csnextmatch(int f, int n) +{ + struct csrecord *r; + struct csmatch *m; + + if (curmatch == NULL) { + if ((r = TAILQ_FIRST(&csrecords)) == NULL) { + ewprintf("The *cscope* buffer does not exist yet"); + return (FALSE); + } + currecord = r; + curmatch = TAILQ_FIRST(&r->matches); + } else { + m = TAILQ_NEXT(curmatch, entry); + if (m == NULL) { + r = TAILQ_NEXT(currecord, entry); + if (r == NULL) { + ewprintf("The end of *cscope* buffer has been" + " reached"); + return (FALSE); + } else { + currecord = r; + curmatch = TAILQ_FIRST(&currecord->matches); + } + } else + curmatch = m; + } + return (jumptomatch()); +} + +/* + * Previous Symbol. Bound to C-c s p + */ +/* ARGSUSED */ +int +csprevmatch(int f, int n) +{ + struct csmatch *m; + struct csrecord *r; + + if (curmatch == NULL) + return (FALSE); + else { + m = TAILQ_PREV(curmatch, matches, entry); + if (m) + curmatch = m; + else { + r = TAILQ_PREV(currecord, csrecords, entry); + if (r == NULL) { + ewprintf("The beginning of *cscope* buffer has" + " been reached"); + return (FALSE); + } else { + currecord = r; + curmatch = TAILQ_LAST(&currecord->matches, + matches); + } + } + } + return (jumptomatch()); +} + +/* + * Next file. + */ +int +csnextfile(int f, int n) +{ + struct csrecord *r; + + if (curmatch == NULL) { + if ((r = TAILQ_FIRST(&csrecords)) == NULL) { + ewprintf("The *cscope* buffer does not exist yet"); + return (FALSE); + } + + } else { + if ((r = TAILQ_NEXT(currecord, entry)) == NULL) { + ewprintf("The end of *cscope* buffer has been reached"); + return (FALSE); + } + } + currecord = r; + curmatch = TAILQ_FIRST(&currecord->matches); + return (jumptomatch()); +} + +/* + * Previous file. + */ +int +csprevfile(int f, int n) +{ + struct csrecord *r; + + if (curmatch == NULL) { + if ((r = TAILQ_FIRST(&csrecords)) == NULL) { + ewprintf("The *cscope* buffer does not exist yet"); + return (FALSE); + } + + } else { + if ((r = TAILQ_PREV(currecord, csrecords, entry)) == NULL) { + ewprintf("The beginning of *cscope* buffer has been" + " reached"); + return (FALSE); + } + } + currecord = r; + curmatch = TAILQ_FIRST(&currecord->matches); + return (jumptomatch()); +} + +/* + * The current symbol location is extracted from currecord->filename and + * curmatch->lineno. Load the file similar to filevisit and goto the + * lineno recorded. + */ +int +jumptomatch(void) +{ + struct buffer *bp; + char *adjf; + + if (curmatch == NULL || currecord == NULL) + return (FALSE); + adjf = adjustname(currecord->filename, TRUE); + if (adjf == NULL) + return (FALSE); + if ((bp = findbuffer(adjf)) == NULL) + return (FALSE); + curbp = bp; + if (showbuffer(bp, curwp, WFFULL) != TRUE) + return (FALSE); + if (bp->b_fname[0] == '\0') { + if (readin(adjf) != TRUE) + killbuffer(bp); + } + gotoline(FFARG, curmatch->lineno); + return (TRUE); + +} + +/* + * Ask for the symbol, construct cscope commandline with the symbol + * and passed in index. Popen cscope, read the output into *cscope* + * buffer and pop it. + */ +int +do_cscope(int i) +{ + struct buffer *bp; + FILE *fpipe; + char pattern[MAX_TOKEN], cmd[BUFSIZ], title[BUFSIZ]; + char *p, *buf; + int clen, nores = 0; + size_t len; + + /* If current buffer isn't a source file just return */ + if (fnmatch("*.[chy]", curbp->b_fname, 0) != 0) { + ewprintf("C-c s not defined"); + return (FALSE); + } + + if (curtoken(0, 1, pattern) == FALSE) + return (FALSE); + p = eread(csprompt[i], pattern, MAX_TOKEN, EFNEW | EFCR | EFDEF); + if (p == NULL) + return (ABORT); + else if (p[0] == '\0') + return (FALSE); + + if (csexists("cscope") == FALSE) { + ewprintf("no such file or directory, cscope"); + return (FALSE); + } + + csflush(); + clen = snprintf(cmd, sizeof(cmd), "cscope -L -%d %s 2>/dev/null", + i, pattern); + if (clen < 0 || clen >= sizeof(cmd)) + return (FALSE); + + if ((fpipe = popen(cmd, "r")) == NULL) { + ewprintf("problem opening pipe"); + return (FALSE); + } + + bp = bfind("*cscope*", TRUE); + if (bclear(bp) != TRUE) + return (FALSE); + bp->b_flag |= BFREADONLY; + + clen = snprintf(title, sizeof(title), "%s%s", csprompt[i], pattern); + if (clen < 0 || clen >= sizeof(title)) + return (FALSE); + addline(bp, title); + addline(bp, ""); + addline(bp, "-------------------------------------------------------------------------------"); + /* All lines are NUL terminated */ + while ((buf = fgetln(fpipe, &len)) != NULL) { + buf[len - 1] = '\0'; + if (addentry(bp, buf) != TRUE) + return (FALSE); + nores = 1; + }; + pclose(fpipe); + addline(bp, "-------------------------------------------------------------------------------"); + if (nores == 0) + ewprintf("No matches were found."); + return (popbuftop(bp, WNONE)); +} + +/* + * For each line read from cscope output, extract the tokens, + * add them to list and pretty print a line in *cscope* buffer. + */ +int +addentry(struct buffer *bp, char *csline) +{ + struct csrecord *r; + struct csmatch *m; + struct cstokens t; + int lineno; + char buf[BUFSIZ]; + const char *errstr; + + r = NULL; + if (getattr(csline, &t) == FALSE) + return (FALSE); + + lineno = strtonum(t.lineno, INT_MIN, INT_MAX, &errstr); + if (errstr) + return (FALSE); + + if (addentryfn == NULL || strcmp(addentryfn, t.fname) != 0) { + if ((r = malloc(sizeof(struct csrecord))) == NULL) + return (FALSE); + addentryr = r; + if ((r->filename = strndup(t.fname, NFILEN)) == NULL) + goto cleanup; + addentryfn = r->filename; + TAILQ_INIT(&r->matches); + if ((m = malloc(sizeof(struct csmatch))) == NULL) + goto cleanup; + m->lineno = lineno; + TAILQ_INSERT_TAIL(&r->matches, m, entry); + TAILQ_INSERT_TAIL(&csrecords, r, entry); + addline(bp, ""); + if (snprintf(buf, sizeof(buf), "*** %s", t.fname) < 0) + goto cleanup; + addline(bp, buf); + } else { + if ((m = malloc(sizeof(struct csmatch))) == NULL) + goto cleanup; + m->lineno = lineno; + TAILQ_INSERT_TAIL(&addentryr->matches, m, entry); + } + prettyprint(bp, &t); + return (TRUE); +cleanup: + free(r); + return (FALSE); +} + +/* + * Cscope line: + */ +int +getattr(char *line, struct cstokens *t) +{ + char *p; + + if ((p = strchr(line, ' ')) == NULL) + return (FALSE); + *p++ = '\0'; + t->fname = line; + line = p; + + if ((p = strchr(line, ' ')) == NULL) + return (FALSE); + *p++ = '\0'; + t->function = line; + line = p; + + if ((p = strchr(line, ' ')) == NULL) + return (FALSE); + *p++ = '\0'; + t->lineno = line; + + if (*p == '\0') + return (FALSE); + t->pattern = p; + + return (TRUE); +} + +void +prettyprint(struct buffer *bp, struct cstokens *t) +{ + char buf[BUFSIZ]; + + if (snprintf(buf, sizeof(buf), "%s[%s]\t\t%s", + t->function, t->lineno, ltrim(t->pattern)) < 0) + return; + addline(bp, buf); +} + +const char * +ltrim(const char *s) +{ + while (isblank(*s)) + s++; + return s; +} + +void +csflush(void) +{ + struct csrecord *r; + struct csmatch *m; + + while ((r = TAILQ_FIRST(&csrecords)) != NULL) { + free(r->filename); + while ((m = TAILQ_FIRST(&r->matches)) != NULL) { + TAILQ_REMOVE(&r->matches, m, entry); + free(m); + } + TAILQ_REMOVE(&csrecords, r, entry); + free(r); + } + addentryr = NULL; + addentryfn = NULL; + currecord = NULL; + curmatch = NULL; +} + +/* + * Check if the cmd exists in $PATH. Split on ":" and iterate through + * all paths in $PATH. + */ +int +csexists(const char *cmd) +{ + char fname[NFILEN], *dir, *path, *pathc, *tmp; + int cmdlen, dlen; + + /* Special case if prog contains '/' */ + if (strchr(cmd, '/')) { + if (access(cmd, F_OK) == -1) + return (FALSE); + else + return (TRUE); + } + if ((tmp = getenv("PATH")) == NULL) + return (FALSE); + if ((pathc = path = strndup(tmp, NFILEN)) == NULL) { + ewprintf("out of memory"); + return (FALSE); + } + cmdlen = strlen(cmd); + while ((dir = strsep(&path, ":")) != NULL) { + if (*dir == '\0') + *dir = '.'; + + dlen = strlen(dir); + while (dir[dlen-1] == '/') + dir[--dlen] = '\0'; /* strip trailing '/' */ + + if (dlen + 1 + cmdlen >= sizeof(fname)) { + ewprintf("path too long"); + goto cleanup; + } + snprintf(fname, sizeof(fname), "%s/%s", dir, cmd); + if(access(fname, F_OK) == 0) { + free(pathc); + return (TRUE); + } + } +cleanup: + free(pathc); + return (FALSE); +} diff --git a/mg/def.h b/mg/def.h new file mode 100644 index 0000000..1284e94 --- /dev/null +++ b/mg/def.h @@ -0,0 +1,707 @@ +/* $OpenBSD: def.h,v 1.124 2012/06/14 17:21:22 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * This file is the general header file for all parts + * of the Mg display editor. It contains all of the + * general definitions and macros. It also contains some + * conditional compilation flags. All of the per-system and + * per-terminal definitions are in special header files. + */ + +#include "sysdef.h" /* Order is critical. */ +#include "ttydef.h" +#include "chrdef.h" + +typedef int (*PF)(int, int); /* generally useful type */ + +/* + * Table sizes, etc. + */ +#define NFILEN 1024 /* Length, file name. */ +#define NBUFN NFILEN /* Length, buffer name. */ +#define NLINE 256 /* Length, line. */ +#define PBMODES 4 /* modes per buffer */ +#define NKBDM 256 /* Length, keyboard macro. */ +#define NPAT 80 /* Length, pattern. */ +#define HUGE 1000 /* A rather large number. */ +#define NSRCH 128 /* Undoable search commands. */ +#define NXNAME 64 /* Length, extended command. */ +#define NKNAME 20 /* Length, key names. */ +#define NTIME 50 /* Length, timestamp string. */ +/* + * Universal. + */ +#define FALSE 0 /* False, no, bad, etc. */ +#define TRUE 1 /* True, yes, good, etc. */ +#define ABORT 2 /* Death, ^G, abort, etc. */ + +#define KCLEAR 2 /* clear echo area */ + +/* + * These flag bits keep track of + * some aspects of the last command. The CFCPCN + * flag controls goal column setting. The CFKILL + * flag controls the clearing versus appending + * of data in the kill buffer. + */ +#define CFCPCN 0x0001 /* Last command was C-P, C-N */ +#define CFKILL 0x0002 /* Last command was a kill */ +#define CFINS 0x0004 /* Last command was self-insert */ + +/* + * File I/O. + */ +#define FIOSUC 0 /* Success. */ +#define FIOFNF 1 /* File not found. */ +#define FIOEOF 2 /* End of file. */ +#define FIOERR 3 /* Error. */ +#define FIOLONG 4 /* long line partially read */ +#define FIODIR 5 /* File is a directory */ + +/* + * Directory I/O. + */ +#define DIOSUC 0 /* Success. */ +#define DIOEOF 1 /* End of file. */ +#define DIOERR 2 /* Error. */ + +/* + * Display colors. + */ +#define CNONE 0 /* Unknown color. */ +#define CTEXT 1 /* Text color. */ +#define CMODE 2 /* Mode line color. */ + +/* + * Flags for keyboard invoked functions. + */ +#define FFUNIV 1 /* universal argument */ +#define FFNEGARG 2 /* negative only argument */ +#define FFOTHARG 4 /* other argument */ +#define FFARG 7 /* any argument */ +#define FFRAND 8 /* Called by other function */ + +/* + * Flags for "eread". + */ +#define EFFUNC 0x0001 /* Autocomplete functions. */ +#define EFBUF 0x0002 /* Autocomplete buffers. */ +#define EFFILE 0x0004 /* " files (maybe someday) */ +#define EFAUTO 0x0007 /* Some autocompletion on */ +#define EFNEW 0x0008 /* New prompt. */ +#define EFCR 0x0010 /* Echo CR at end; last read. */ +#define EFDEF 0x0020 /* buffer contains default args */ +#define EFNUL 0x0040 /* Null Minibuffer OK */ + +/* + * Direction of insert into kill ring + */ +#define KNONE 0x00 +#define KFORW 0x01 /* forward insert into kill ring */ +#define KBACK 0x02 /* Backwards insert into kill ring */ +#define KREG 0x04 /* This is a region-based kill */ + +#define MAX_TOKEN 64 + +/* + * This structure holds the starting position + * (as a line/offset pair) and the number of characters in a + * region of a buffer. This makes passing the specification + * of a region around a little bit easier. + */ +struct region { + struct line *r_linep; /* Origin line address. */ + int r_offset; /* Origin line offset. */ + int r_lineno; /* Origin line number */ + RSIZE r_size; /* Length in characters. */ +}; + + +/* + * All text is kept in circularly linked + * lists of "line" structures. These begin at the + * header line (which is the blank line beyond the + * end of the buffer). This line is pointed to by + * the "buffer" structure. Each line contains the number of + * bytes in the line (the "used" size), the size + * of the text array, and the text. The end of line + * is not stored as a byte; it's implied. Future + * additions will include update hints, and a + * list of marks into the line. + */ +struct line { + struct line *l_fp; /* Link to the next line */ + struct line *l_bp; /* Link to the previous line */ + int l_size; /* Allocated size */ + int l_used; /* Used size */ + char *l_text; /* Content of the line */ +}; + +/* + * The rationale behind these macros is that you + * could (with some editing, like changing the type of a line + * link from a "struct line *" to a "REFLINE", and fixing the commands + * like file reading that break the rules) change the actual + * storage representation of lines to use something fancy on + * machines with small address spaces. + */ +#define lforw(lp) ((lp)->l_fp) +#define lback(lp) ((lp)->l_bp) +#define lgetc(lp, n) (CHARMASK((lp)->l_text[(n)])) +#define lputc(lp, n, c) ((lp)->l_text[(n)]=(c)) +#define llength(lp) ((lp)->l_used) +#define ltext(lp) ((lp)->l_text) + +/* + * All repeated structures are kept as linked lists of structures. + * All of these start with a LIST structure (except lines, which + * have their own abstraction). This will allow for + * later conversion to generic list manipulation routines should + * I decide to do that. It does mean that there are four extra + * bytes per window. I feel that this is an acceptable price, + * considering that there are usually only one or two windows. + */ +struct list { + union { + struct mgwin *l_wp; + struct buffer *x_bp; /* l_bp is used by struct line */ + struct list *l_nxt; + } l_p; + char *l_name; +}; + +/* + * Usual hack - to keep from uglifying the code with lotsa + * references through the union, we #define something for it. + */ +#define l_next l_p.l_nxt + +/* + * There is a window structure allocated for + * every active display window. The windows are kept in a + * big list, in top to bottom screen order, with the listhead at + * "wheadp". Each window contains its own values of dot and mark. + * The flag field contains some bits that are set by commands + * to guide redisplay; although this is a bit of a compromise in + * terms of decoupling, the full blown redisplay is just too + * expensive to run for every input character. + */ +struct mgwin { + struct list w_list; /* List header */ + struct buffer *w_bufp; /* Buffer displayed in window */ + struct line *w_linep; /* Top line in the window */ + struct line *w_dotp; /* Line containing "." */ + struct line *w_markp; /* Line containing "mark" */ + int w_doto; /* Byte offset for "." */ + int w_marko; /* Byte offset for "mark" */ + int w_toprow; /* Origin 0 top row of window */ + int w_ntrows; /* # of rows of text in window */ + int w_frame; /* #lines to reframe by. */ + char w_rflag; /* Redisplay Flags. */ + char w_flag; /* Flags. */ + struct line *w_wrapline; + int w_dotline; /* current line number of dot */ + int w_markline; /* current line number of mark */ +}; +#define w_wndp w_list.l_p.l_wp +#define w_name w_list.l_name + +/* + * Window redisplay flags are set by command processors to + * tell the display system what has happened to the buffer + * mapped by the window. Setting "WFFULL" is always a safe thing + * to do, but it may do more work than is necessary. Always try + * to set the simplest action that achieves the required update. + * Because commands set bits in the "w_flag", update will see + * all change flags, and do the most general one. + */ +#define WFFRAME 0x01 /* Force reframe. */ +#define WFMOVE 0x02 /* Movement from line to line. */ +#define WFEDIT 0x04 /* Editing within a line. */ +#define WFFULL 0x08 /* Do a full display. */ +#define WFMODE 0x10 /* Update mode line. */ + +/* + * Window flags + */ +#define WNONE 0x00 /* No special window options. */ +#define WEPHEM 0x01 /* Window is ephemeral. */ + +struct undo_rec; +TAILQ_HEAD(undoq, undo_rec); + +/* + * Text is kept in buffers. A buffer header, described + * below, exists for every buffer in the system. The buffers are + * kept in a big list, so that commands that search for a buffer by + * name can find the buffer header. There is a safe store for the + * dot and mark in the header, but this is only valid if the buffer + * is not being displayed (that is, if "b_nwnd" is 0). The text for + * the buffer is kept in a circularly linked list of lines, with + * a pointer to the header line in "b_headp". + */ +struct buffer { + struct list b_list; /* buffer list pointer */ + struct buffer *b_altb; /* Link to alternate buffer */ + struct line *b_dotp; /* Link to "." line structure */ + struct line *b_markp; /* ditto for mark */ + struct line *b_headp; /* Link to the header line */ + struct maps_s *b_modes[PBMODES]; /* buffer modes */ + int b_doto; /* Offset of "." in above line */ + int b_marko; /* ditto for the "mark" */ + short b_nmodes; /* number of non-fundamental modes */ + char b_nwnd; /* Count of windows on buffer */ + char b_flag; /* Flags */ + char b_fname[NFILEN]; /* File name */ + char b_cwd[NFILEN]; /* working directory */ + struct fileinfo b_fi; /* File attributes */ + struct undoq b_undo; /* Undo actions list */ + int b_undopos; /* Where we were during last undo */ + struct undo_rec *b_undoptr; + int b_dotline; /* Line number of dot */ + int b_markline; /* Line number of mark */ + int b_lines; /* Number of lines in file */ +}; +#define b_bufp b_list.l_p.x_bp +#define b_bname b_list.l_name + +/* Some helper macros, in case they ever change to functions */ +#define bfirstlp(buf) (lforw((buf)->b_headp)) +#define blastlp(buf) (lback((buf)->b_headp)) + +#define BFCHG 0x01 /* Changed. */ +#define BFBAK 0x02 /* Need to make a backup. */ +#ifdef NOTAB +#define BFNOTAB 0x04 /* no tab mode */ +#endif +#define BFOVERWRITE 0x08 /* overwrite mode */ +#define BFREADONLY 0x10 /* read only mode */ +#define BFDIRTY 0x20 /* Buffer was modified elsewhere */ +#define BFIGNDIRTY 0x40 /* Ignore modifications */ +/* + * This structure holds information about recent actions for the Undo command. + */ +struct undo_rec { + TAILQ_ENTRY(undo_rec) next; + enum { + INSERT = 1, + DELETE, + BOUNDARY, + MODIFIED, + DELREG + } type; + struct region region; + int pos; + char *content; +}; + +/* + * Prototypes. + */ + +/* tty.c X */ +void ttinit(void); +void ttreinit(void); +void tttidy(void); +void ttmove(int, int); +void tteeol(void); +void tteeop(void); +void ttbeep(void); +void ttinsl(int, int, int); +void ttdell(int, int, int); +void ttwindow(int, int); +void ttnowindow(void); +void ttcolor(int); +void ttresize(void); + +volatile sig_atomic_t winch_flag; + +/* ttyio.c */ +void ttopen(void); +int ttraw(void); +void ttclose(void); +int ttcooked(void); +int ttputc(int); +void ttflush(void); +int ttgetc(void); +int ttwait(int); +int charswaiting(void); + +/* dir.c */ +void dirinit(void); +int changedir(int, int); +int showcwdir(int, int); +int getcwdir(char *, size_t); + +/* dired.c */ +struct buffer *dired_(char *); + +/* file.c X */ +int fileinsert(int, int); +int filevisit(int, int); +int filevisitalt(int, int); +int filevisitro(int, int); +int poptofile(int, int); +struct buffer *findbuffer(char *); +int readin(char *); +int insertfile(char *, char *, int); +int filewrite(int, int); +int filesave(int, int); +int buffsave(struct buffer *); +int makebkfile(int, int); +int writeout(FILE **, struct buffer *, char *); +void upmodes(struct buffer *); +size_t xbasename(char *, const char *, size_t); + +/* line.c X */ +struct line *lalloc(int); +int lrealloc(struct line *, int); +void lfree(struct line *); +void lchange(int); +int linsert_str(const char *, int); +int linsert(int, int); +int lnewline_at(struct line *, int); +int lnewline(void); +int ldelete(RSIZE, int); +int ldelnewline(void); +int lreplace(RSIZE, char *); +char * linetostr(const struct line *); + +/* yank.c X */ + +void kdelete(void); +int kinsert(int, int); +int kremove(int); +int kchunk(char *, RSIZE, int); +int killline(int, int); +int yank(int, int); + +/* window.c X */ +struct mgwin *new_window(struct buffer *); +void free_window(struct mgwin *); +int reposition(int, int); +int redraw(int, int); +int do_redraw(int, int, int); +int nextwind(int, int); +int prevwind(int, int); +int onlywind(int, int); +int splitwind(int, int); +int enlargewind(int, int); +int shrinkwind(int, int); +int delwind(int, int); + +/* buffer.c */ +int togglereadonly(int, int); +struct buffer *bfind(const char *, int); +int poptobuffer(int, int); +int killbuffer(struct buffer *); +int killbuffer_cmd(int, int); +int savebuffers(int, int); +int listbuffers(int, int); +int addlinef(struct buffer *, char *, ...); +#define addline(bp, text) addlinef(bp, "%s", text) +int anycb(int); +int bclear(struct buffer *); +int showbuffer(struct buffer *, struct mgwin *, int); +int augbname(char *, const char *, size_t); +struct mgwin *popbuf(struct buffer *, int); +int bufferinsert(int, int); +int usebuffer(int, int); +int notmodified(int, int); +int popbuftop(struct buffer *, int); +int getbufcwd(char *, size_t); +int checkdirty(struct buffer *); + +/* display.c */ +int vtresize(int, int, int); +void vtinit(void); +void vttidy(void); +void update(void); +int linenotoggle(int, int); + +/* echo.c X */ +void eerase(void); +int eyorn(const char *); +int eyesno(const char *); +void ewprintf(const char *fmt, ...); +char *ereply(const char *, char *, size_t, ...); +char *eread(const char *, char *, size_t, int, ...); +int getxtra(struct list *, struct list *, int, int); +void free_file_list(struct list *); + +/* fileio.c */ +int ffropen(FILE **, const char *, struct buffer *); +void ffstat(FILE *, struct buffer *); +int ffwopen(FILE **, const char *, struct buffer *); +int ffclose(FILE *, struct buffer *); +int ffputbuf(FILE *, struct buffer *); +int ffgetline(FILE *, char *, int, int *); +int fbackupfile(const char *); +char *adjustname(const char *, int); +char *startupfile(char *); +int copy(char *, char *); +struct list *make_file_list(char *); +int fisdir(const char *); +int fchecktime(struct buffer *); +int fupdstat(struct buffer *); +int backuptohomedir(int, int); +int toggleleavetmp(int, int); + +/* kbd.c X */ +int do_meta(int, int); +int bsmap(int, int); +void ungetkey(int); +int getkey(int); +int doin(void); +int rescan(int, int); +int universal_argument(int, int); +int digit_argument(int, int); +int negative_argument(int, int); +int selfinsert(int, int); +int quote(int, int); + +/* main.c */ +int ctrlg(int, int); +int quit(int, int); + +/* ttyio.c */ +void panic(char *); + +/* cinfo.c */ +char *getkeyname(char *, size_t, int); + +/* basic.c */ +int gotobol(int, int); +int backchar(int, int); +int gotoeol(int, int); +int forwchar(int, int); +int gotobob(int, int); +int gotoeob(int, int); +int forwline(int, int); +int backline(int, int); +void setgoal(void); +int getgoal(struct line *); +int forwpage(int, int); +int backpage(int, int); +int forw1page(int, int); +int back1page(int, int); +int pagenext(int, int); +void isetmark(void); +int setmark(int, int); +int clearmark(int, int); +int swapmark(int, int); +int gotoline(int, int); + +/* random.c X */ +int showcpos(int, int); +int getcolpos(void); +int twiddle(int, int); +int openline(int, int); +int newline(int, int); +int deblank(int, int); +int justone(int, int); +int delwhite(int, int); +int delleadwhite(int, int); +int deltrailwhite(int, int); +int lfindent(int, int); +int indent(int, int); +int forwdel(int, int); +int backdel(int, int); +int space_to_tabstop(int, int); +int backtoindent(int, int); +int joinline(int, int); + +/* tags.c X */ +int findtag(int, int); +int poptag(int, int); +int tagsvisit(int, int); +int curtoken(int, int, char *); + +/* cscope.c */ +int cssymbol(int, int); +int csdefinition(int, int); +int csfuncalled(int, int); +int cscallerfuncs(int, int); +int csfindtext(int, int); +int csegrep(int, int); +int csfindfile(int, int); +int csfindinc(int, int); +int csnextfile(int, int); +int csnextmatch(int, int); +int csprevfile(int, int); +int csprevmatch(int, int); +int cscreatelist(int, int); + +/* extend.c X */ +int insert(int, int); +int bindtokey(int, int); +int localbind(int, int); +int redefine_key(int, int); +int unbindtokey(int, int); +int localunbind(int, int); +int extend(int, int); +int evalexpr(int, int); +int evalbuffer(int, int); +int evalfile(int, int); +int load(const char *); +int excline(char *); + +/* help.c X */ +int desckey(int, int); +int wallchart(int, int); +int help_help(int, int); +int apropos_command(int, int); + +/* paragraph.c X */ +int gotobop(int, int); +int gotoeop(int, int); +int fillpara(int, int); +int killpara(int, int); +int fillword(int, int); +int setfillcol(int, int); + +/* word.c X */ +int backword(int, int); +int forwword(int, int); +int upperword(int, int); +int lowerword(int, int); +int capword(int, int); +int delfword(int, int); +int delbword(int, int); +int inword(void); + +/* region.c X */ +int killregion(int, int); +int copyregion(int, int); +int lowerregion(int, int); +int upperregion(int, int); +int prefixregion(int, int); +int setprefix(int, int); +int region_get_data(struct region *, char *, int); +void region_put_data(const char *, int); +int markbuffer(int, int); +int piperegion(int, int); + +/* search.c X */ +int forwsearch(int, int); +int backsearch(int, int); +int searchagain(int, int); +int forwisearch(int, int); +int backisearch(int, int); +int queryrepl(int, int); +int forwsrch(void); +int backsrch(void); +int readpattern(char *); + +/* spawn.c X */ +int spawncli(int, int); + +/* ttykbd.c X */ +void ttykeymapinit(void); +void ttykeymaptidy(void); + +/* match.c X */ +int showmatch(int, int); + +/* version.c X */ +int showversion(int, int); + +/* macro.c X */ +int definemacro(int, int); +int finishmacro(int, int); +int executemacro(int, int); + +/* modes.c X */ +int indentmode(int, int); +int fillmode(int, int); +int blinkparen(int, int); +#ifdef NOTAB +int notabmode(int, int); +#endif /* NOTAB */ +int overwrite_mode(int, int); +int set_default_mode(int,int); + +#ifdef REGEX +/* re_search.c X */ +int re_forwsearch(int, int); +int re_backsearch(int, int); +int re_searchagain(int, int); +int re_queryrepl(int, int); +int replstr(int, int); +int setcasefold(int, int); +int delmatchlines(int, int); +int delnonmatchlines(int, int); +int cntmatchlines(int, int); +int cntnonmatchlines(int, int); +#endif /* REGEX */ + +/* undo.c X */ +void free_undo_record(struct undo_rec *); +int undo_dump(int, int); +int undo_enabled(void); +int undo_enable(int, int); +int undo_add_boundary(int, int); +void undo_add_modified(void); +int undo_add_insert(struct line *, int, int); +int undo_add_delete(struct line *, int, int, int); +int undo_boundary_enable(int, int); +int undo_add_change(struct line *, int, int); +int undo(int, int); + +/* autoexec.c X */ +int auto_execute(int, int); +PF *find_autoexec(const char *); +int add_autoexec(const char *, const char *); + +/* cmode.c X */ +int cmode(int, int); +int cc_brace(int, int); +int cc_char(int, int); +int cc_tab(int, int); +int cc_indent(int, int); +int cc_lfindent(int, int); + +/* grep.c X */ +int next_error(int, int); +int globalwdtoggle(int, int); +int compile(int, int); + +/* + * Externals. + */ +extern struct buffer *bheadp; +extern struct buffer *curbp; +extern struct mgwin *curwp; +extern struct mgwin *wheadp; +extern int thisflag; +extern int lastflag; +extern int curgoal; +extern int startrow; +extern int epresf; +extern int sgarbf; +extern int mode; +extern int nrow; +extern int ncol; +extern int ttrow; +extern int ttcol; +extern int tttop; +extern int ttbot; +extern int tthue; +extern int defb_nmodes; +extern int defb_flag; +extern char cinfo[]; +extern char *keystrings[]; +extern char pat[NPAT]; +#ifndef NO_DPROMPT +extern char prompt[]; +#endif /* !NO_DPROMPT */ + +/* + * Globals. + */ +int tceeol; +int tcinsl; +int tcdell; +int rptcount; /* successive invocation count */ diff --git a/mg/dir.c b/mg/dir.c new file mode 100644 index 0000000..2352773 --- /dev/null +++ b/mg/dir.c @@ -0,0 +1,77 @@ +/* $OpenBSD: dir.c,v 1.19 2008/06/13 20:07:40 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * Name: MG 2a + * Directory management functions + * Created: Ron Flax (ron@vsedev.vse.com) + * Modified for MG 2a by Mic Kaczmarczik 03-Aug-1987 + */ + +#include "def.h" + +static char mgcwd[NFILEN]; + +/* + * Initialize anything the directory management routines need. + */ +void +dirinit(void) +{ + mgcwd[0] = '\0'; + if (getcwd(mgcwd, sizeof(mgcwd)) == NULL) { + ewprintf("Can't get current directory!"); + chdir("/"); + } + if (!(mgcwd[0] == '/' && mgcwd [1] == '\0')) + (void)strlcat(mgcwd, "/", sizeof(mgcwd)); +} + +/* + * Change current working directory. + */ +/* ARGSUSED */ +int +changedir(int f, int n) +{ + char bufc[NFILEN], *bufp; + + (void)strlcpy(bufc, mgcwd, sizeof(bufc)); + if ((bufp = eread("Change default directory: ", bufc, NFILEN, + EFDEF | EFNEW | EFCR | EFFILE)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + /* Append trailing slash */ + if (chdir(bufc) == -1) { + ewprintf("Can't change dir to %s", bufc); + return (FALSE); + } + if ((bufp = getcwd(mgcwd, sizeof(mgcwd))) == NULL) + panic("Can't get current directory!"); + if (mgcwd[strlen(mgcwd) - 1] != '/') + (void)strlcat(mgcwd, "/", sizeof(mgcwd)); + ewprintf("Current directory is now %s", bufp); + return (TRUE); +} + +/* + * Show current directory. + */ +/* ARGSUSED */ +int +showcwdir(int f, int n) +{ + ewprintf("Current directory: %s", mgcwd); + return (TRUE); +} + +int +getcwdir(char *buf, size_t len) +{ + if (strlcpy(buf, mgcwd, len) >= len) + return (FALSE); + + return (TRUE); +} diff --git a/mg/dired.c b/mg/dired.c new file mode 100644 index 0000000..184c9f7 --- /dev/null +++ b/mg/dired.c @@ -0,0 +1,779 @@ +/* $OpenBSD: dired.c,v 1.51 2012/03/14 13:56:35 lum Exp $ */ + +/* This file is in the public domain. */ + +/* dired module for mg 2a + * by Robert A. Larson + */ + +#include "def.h" +#include "funmap.h" +#include "kbd.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +void dired_init(void); +static int dired(int, int); +static int d_otherwindow(int, int); +static int d_undel(int, int); +static int d_undelbak(int, int); +static int d_findfile(int, int); +static int d_ffotherwindow(int, int); +static int d_expunge(int, int); +static int d_copy(int, int); +static int d_del(int, int); +static int d_rename(int, int); +static int d_exec(int, struct buffer *, const char *, const char *, ...); +static int d_shell_command(int, int); +static int d_create_directory(int, int); +static int d_makename(struct line *, char *, size_t); +static int d_warpdot(struct line *, int *); +static int d_forwpage(int, int); +static int d_backpage(int, int); +static int d_forwline(int, int); +static int d_backline(int, int); +static void reaper(int); + +extern struct keymap_s helpmap, cXmap, metamap; + +static PF dirednul[] = { + setmark, /* ^@ */ + gotobol, /* ^A */ + backchar, /* ^B */ + rescan, /* ^C */ + d_del, /* ^D */ + gotoeol, /* ^E */ + forwchar, /* ^F */ + ctrlg, /* ^G */ + NULL, /* ^H */ +}; + +static PF diredcl[] = { + reposition, /* ^L */ + d_findfile, /* ^M */ + d_forwline, /* ^N */ + rescan, /* ^O */ + d_backline, /* ^P */ + rescan, /* ^Q */ + backisearch, /* ^R */ + forwisearch, /* ^S */ + rescan, /* ^T */ + universal_argument, /* ^U */ + d_forwpage, /* ^V */ + rescan, /* ^W */ + NULL /* ^X */ +}; + +static PF diredcz[] = { + spawncli, /* ^Z */ + NULL, /* esc */ + rescan, /* ^\ */ + rescan, /* ^] */ + rescan, /* ^^ */ + rescan, /* ^_ */ + d_forwline, /* SP */ + d_shell_command, /* ! */ + rescan, /* " */ + rescan, /* # */ + rescan, /* $ */ + rescan, /* % */ + rescan, /* & */ + rescan, /* ' */ + rescan, /* ( */ + rescan, /* ) */ + rescan, /* * */ + d_create_directory /* + */ +}; + +static PF diredc[] = { + d_copy, /* c */ + d_del, /* d */ + d_findfile, /* e */ + d_findfile /* f */ +}; + +static PF diredn[] = { + d_forwline, /* n */ + d_ffotherwindow, /* o */ + d_backline, /* p */ + rescan, /* q */ + d_rename, /* r */ + rescan, /* s */ + rescan, /* t */ + d_undel, /* u */ + rescan, /* v */ + rescan, /* w */ + d_expunge /* x */ +}; + +static PF direddl[] = { + d_undelbak /* del */ +}; + +static PF diredbp[] = { + d_backpage /* v */ +}; + +static PF dirednull[] = { + NULL +}; + +#ifndef DIRED_XMAPS +#define NDIRED_XMAPS 0 /* number of extra map sections */ +#endif /* DIRED_XMAPS */ + +static struct KEYMAPE (1 + IMAPEXT) d_backpagemap = { + 1, + 1 + IMAPEXT, + rescan, + { + { + 'v', 'v', diredbp, NULL + } + } +}; + +static struct KEYMAPE (7 + NDIRED_XMAPS + IMAPEXT) diredmap = { + 7 + NDIRED_XMAPS, + 7 + NDIRED_XMAPS + IMAPEXT, + rescan, + { + { + CCHR('@'), CCHR('H'), dirednul, (KEYMAP *) & helpmap + }, + { + CCHR('L'), CCHR('X'), diredcl, (KEYMAP *) & cXmap + }, + { + CCHR('['), CCHR('['), dirednull, (KEYMAP *) & + d_backpagemap + }, + { + CCHR('Z'), '+', diredcz, (KEYMAP *) & metamap + }, + { + 'c', 'f', diredc, NULL + }, + { + 'n', 'x', diredn, NULL + }, + { + CCHR('?'), CCHR('?'), direddl, NULL + }, +#ifdef DIRED_XMAPS + DIRED_XMAPS, /* map sections for dired mode keys */ +#endif /* DIRED_XMAPS */ + } +}; + +void +dired_init(void) +{ + funmap_add(dired, "dired"); + funmap_add(d_undelbak, "dired-backup-unflag"); + funmap_add(d_copy, "dired-copy-file"); + funmap_add(d_expunge, "dired-do-deletions"); + funmap_add(d_findfile, "dired-find-file"); + funmap_add(d_ffotherwindow, "dired-find-file-other-window"); + funmap_add(d_del, "dired-flag-file-deleted"); + funmap_add(d_forwline, "dired-next-line"); + funmap_add(d_otherwindow, "dired-other-window"); + funmap_add(d_backline, "dired-previous-line"); + funmap_add(d_rename, "dired-rename-file"); + funmap_add(d_backpage, "dired-scroll-down"); + funmap_add(d_forwpage, "dired-scroll-up"); + funmap_add(d_undel, "dired-unflag"); + maps_add((KEYMAP *)&diredmap, "dired"); + dobindkey(fundamental_map, "dired", "^Xd"); +} + +/* ARGSUSED */ +int +dired(int f, int n) +{ + char dname[NFILEN], *bufp, *slash; + struct buffer *bp; + + if (curbp->b_fname && curbp->b_fname[0] != '\0') { + (void)strlcpy(dname, curbp->b_fname, sizeof(dname)); + if ((slash = strrchr(dname, '/')) != NULL) { + *(slash + 1) = '\0'; + } + } else { + if (getcwd(dname, sizeof(dname)) == NULL) + dname[0] = '\0'; + } + + if ((bufp = eread("Dired: ", dname, NFILEN, + EFDEF | EFNEW | EFCR)) == NULL) + return (ABORT); + if (bufp[0] == '\0') + return (FALSE); + if ((bp = dired_(bufp)) == NULL) + return (FALSE); + + curbp = bp; + return (showbuffer(bp, curwp, WFFULL | WFMODE)); +} + +/* ARGSUSED */ +int +d_otherwindow(int f, int n) +{ + char dname[NFILEN], *bufp, *slash; + struct buffer *bp; + struct mgwin *wp; + + if (curbp->b_fname && curbp->b_fname[0] != '\0') { + (void)strlcpy(dname, curbp->b_fname, sizeof(dname)); + if ((slash = strrchr(dname, '/')) != NULL) { + *(slash + 1) = '\0'; + } + } else { + if (getcwd(dname, sizeof(dname)) == NULL) + dname[0] = '\0'; + } + + if ((bufp = eread("Dired other window: ", dname, NFILEN, + EFDEF | EFNEW | EFCR)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + if ((bp = dired_(bufp)) == NULL) + return (FALSE); + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + curbp = bp; + curwp = wp; + return (TRUE); +} + +/* ARGSUSED */ +int +d_del(int f, int n) +{ + if (n < 0) + return (FALSE); + while (n--) { + if (llength(curwp->w_dotp) > 0) + lputc(curwp->w_dotp, 0, 'D'); + if (lforw(curwp->w_dotp) != curbp->b_headp) + curwp->w_dotp = lforw(curwp->w_dotp); + } + curwp->w_rflag |= WFEDIT | WFMOVE; + curwp->w_doto = 0; + return (TRUE); +} + +/* ARGSUSED */ +int +d_undel(int f, int n) +{ + if (n < 0) + return (d_undelbak(f, -n)); + while (n--) { + if (llength(curwp->w_dotp) > 0) + lputc(curwp->w_dotp, 0, ' '); + if (lforw(curwp->w_dotp) != curbp->b_headp) + curwp->w_dotp = lforw(curwp->w_dotp); + } + curwp->w_rflag |= WFEDIT | WFMOVE; + curwp->w_doto = 0; + return (TRUE); +} + +/* ARGSUSED */ +int +d_undelbak(int f, int n) +{ + if (n < 0) + return (d_undel(f, -n)); + while (n--) { + if (llength(curwp->w_dotp) > 0) + lputc(curwp->w_dotp, 0, ' '); + if (lback(curwp->w_dotp) != curbp->b_headp) + curwp->w_dotp = lback(curwp->w_dotp); + } + curwp->w_doto = 0; + curwp->w_rflag |= WFEDIT | WFMOVE; + return (TRUE); +} + +/* ARGSUSED */ +int +d_findfile(int f, int n) +{ + struct buffer *bp; + int s; + char fname[NFILEN]; + + if ((s = d_makename(curwp->w_dotp, fname, sizeof(fname))) == ABORT) + return (FALSE); + if (s == TRUE) + bp = dired_(fname); + else + bp = findbuffer(fname); + if (bp == NULL) + return (FALSE); + curbp = bp; + if (showbuffer(bp, curwp, WFFULL) != TRUE) + return (FALSE); + if (bp->b_fname[0] != 0) + return (TRUE); + return (readin(fname)); +} + +/* ARGSUSED */ +int +d_ffotherwindow(int f, int n) +{ + char fname[NFILEN]; + int s; + struct buffer *bp; + struct mgwin *wp; + + if ((s = d_makename(curwp->w_dotp, fname, sizeof(fname))) == ABORT) + return (FALSE); + if ((bp = (s ? dired_(fname) : findbuffer(fname))) == NULL) + return (FALSE); + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + curbp = bp; + curwp = wp; + if (bp->b_fname[0] != 0) + return (TRUE); /* never true for dired buffers */ + return (readin(fname)); +} + +/* ARGSUSED */ +int +d_expunge(int f, int n) +{ + struct line *lp, *nlp; + char fname[NFILEN], sname[NFILEN]; + + for (lp = bfirstlp(curbp); lp != curbp->b_headp; lp = nlp) { + nlp = lforw(lp); + if (llength(lp) && lgetc(lp, 0) == 'D') { + switch (d_makename(lp, fname, sizeof(fname))) { + case ABORT: + ewprintf("Bad line in dired buffer"); + return (FALSE); + case FALSE: + if (unlink(fname) < 0) { + (void)xbasename(sname, fname, NFILEN); + ewprintf("Could not delete '%s'", sname); + return (FALSE); + } + break; + case TRUE: + if (rmdir(fname) < 0) { + (void)xbasename(sname, fname, NFILEN); + ewprintf("Could not delete directory " + "'%s'", sname); + return (FALSE); + } + break; + } + lfree(lp); + curwp->w_bufp->b_lines--; + curwp->w_rflag |= WFFULL; + } + } + return (TRUE); +} + +/* ARGSUSED */ +int +d_copy(int f, int n) +{ + char frname[NFILEN], toname[NFILEN], sname[NFILEN], *bufp; + int ret; + size_t off; + struct buffer *bp; + + if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) { + ewprintf("Not a file"); + return (FALSE); + } + off = strlcpy(toname, curbp->b_fname, sizeof(toname)); + if (off >= sizeof(toname) - 1) { /* can't happen, really */ + ewprintf("Directory name too long"); + return (FALSE); + } + (void)xbasename(sname, frname, NFILEN); + bufp = eread("Copy %s to: ", toname, sizeof(toname), + EFDEF | EFNEW | EFCR, sname); + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + ret = (copy(frname, toname) >= 0) ? TRUE : FALSE; + if (ret != TRUE) + return (ret); + bp = dired_(curbp->b_fname); + return (showbuffer(bp, curwp, WFFULL | WFMODE)); +} + +/* ARGSUSED */ +int +d_rename(int f, int n) +{ + char frname[NFILEN], toname[NFILEN], *bufp; + int ret; + size_t off; + struct buffer *bp; + char sname[NFILEN]; + + if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) { + ewprintf("Not a file"); + return (FALSE); + } + off = strlcpy(toname, curbp->b_fname, sizeof(toname)); + if (off >= sizeof(toname) - 1) { /* can't happen, really */ + ewprintf("Directory name too long"); + return (FALSE); + } + (void)xbasename(sname, frname, NFILEN); + bufp = eread("Rename %s to: ", toname, + sizeof(toname), EFDEF | EFNEW | EFCR, sname); + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + ret = (rename(frname, toname) >= 0) ? TRUE : FALSE; + if (ret != TRUE) + return (ret); + bp = dired_(curbp->b_fname); + return (showbuffer(bp, curwp, WFFULL | WFMODE)); +} + +/* ARGSUSED */ +void +reaper(int signo __attribute__((unused))) +{ + int save_errno = errno, status; + + while (waitpid(-1, &status, WNOHANG) >= 0) + ; + errno = save_errno; +} + +/* + * Pipe the currently selected file through a shell command. + */ +/* ARGSUSED */ +int +d_shell_command(int f, int n) +{ + char command[512], fname[MAXPATHLEN], *bufp; + struct buffer *bp; + struct mgwin *wp; + char sname[NFILEN]; + + bp = bfind("*Shell Command Output*", TRUE); + if (bclear(bp) != TRUE) + return (ABORT); + + if (d_makename(curwp->w_dotp, fname, sizeof(fname)) != FALSE) { + ewprintf("bad line"); + return (ABORT); + } + + command[0] = '\0'; + (void)xbasename(sname, fname, NFILEN); + bufp = eread("! on %s: ", command, sizeof(command), EFNEW, sname); + if (bufp == NULL) + return (ABORT); + + if (d_exec(0, bp, fname, "sh", "-c", command, NULL) != TRUE) + return (ABORT); + + if ((wp = popbuf(bp, WNONE)) == NULL) + return (ABORT); /* XXX - free the buffer?? */ + curwp = wp; + curbp = wp->w_bufp; + return (TRUE); +} + +/* + * Pipe input file to cmd and insert the command's output in the + * given buffer. Each line will be prefixed with the given + * number of spaces. + */ +static int +d_exec(int space, struct buffer *bp, const char *input, const char *cmd, ...) +{ + char buf[BUFSIZ]; + va_list ap; + struct sigaction olda, newa; + char **argv = NULL, *cp; + FILE *fin; + int fds[2] = { -1, -1 }; + int infd = -1; + int ret = (ABORT), n; + pid_t pid; + + if (sigaction(SIGCHLD, NULL, &olda) == -1) + return (ABORT); + + /* Find the number of arguments. */ + va_start(ap, cmd); + for (n = 2; va_arg(ap, char *) != NULL; n++) + ; + va_end(ap); + + /* Allocate and build the argv. */ + if ((argv = calloc(n, sizeof(*argv))) == NULL) { + ewprintf("Can't allocate argv : %s", strerror(errno)); + goto out; + } + + n = 1; + argv[0] = (char *)cmd; + va_start(ap, cmd); + while ((argv[n] = va_arg(ap, char *)) != NULL) + n++; + va_end(ap); + + if (input == NULL) + input = "/dev/null"; + + if ((infd = open(input, O_RDONLY)) == -1) { + ewprintf("Can't open input file : %s", strerror(errno)); + goto out; + } + + if (pipe(fds) == -1) { + ewprintf("Can't create pipe : %s", strerror(errno)); + goto out; + } + + newa.sa_handler = reaper; + newa.sa_flags = 0; + if (sigaction(SIGCHLD, &newa, NULL) == -1) + goto out; + + if ((pid = fork()) == -1) { + ewprintf("Can't fork"); + goto out; + } + + switch (pid) { + case 0: /* Child */ + close(fds[0]); + dup2(infd, STDIN_FILENO); + dup2(fds[1], STDOUT_FILENO); + dup2(fds[1], STDERR_FILENO); + if (execvp(argv[0], argv) == -1) + ewprintf("Can't exec %s: %s", argv[0], strerror(errno)); + exit(1); + break; + default: /* Parent */ + close(infd); + close(fds[1]); + infd = fds[1] = -1; + if ((fin = fdopen(fds[0], "r")) == NULL) + goto out; + while (fgets(buf, sizeof(buf), fin) != NULL) { + cp = strrchr(buf, '\n'); + if (cp == NULL && !feof(fin)) { /* too long a line */ + int c; + addlinef(bp, "%*s%s...", space, "", buf); + while ((c = getc(fin)) != EOF && c != '\n') + ; + continue; + } else if (cp) + *cp = '\0'; + addlinef(bp, "%*s%s", space, "", buf); + } + fclose(fin); + break; + } + ret = (TRUE); + +out: + if (sigaction(SIGCHLD, &olda, NULL) == -1) + ewprintf("Warning, couldn't reset previous signal handler"); + if (fds[0] != -1) + close(fds[0]); + if (fds[1] != -1) + close(fds[1]); + if (infd != -1) + close(infd); + if (argv != NULL) + free(argv); + return ret; +} + +/* ARGSUSED */ +int +d_create_directory(int f, int n) +{ + char tocreate[MAXPATHLEN], *bufp; + size_t off; + struct buffer *bp; + + off = strlcpy(tocreate, curbp->b_fname, sizeof(tocreate)); + if (off >= sizeof(tocreate) - 1) + return (FALSE); + if ((bufp = eread("Create directory: ", tocreate, + sizeof(tocreate), EFDEF | EFNEW | EFCR)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + if (mkdir(tocreate, 0755) == -1) { + ewprintf("Creating directory: %s, %s", strerror(errno), + tocreate); + return (FALSE); + } + bp = dired_(curbp->b_fname); + return (showbuffer(bp, curwp, WFFULL | WFMODE)); +} + +static int +d_makename(struct line *lp, char *fn, size_t len) +{ + int start, nlen; + char *namep; + + if (d_warpdot(lp, &start) == FALSE) + return (ABORT); + namep = &lp->l_text[start]; + nlen = llength(lp) - start; + + if (snprintf(fn, len, "%s%.*s", curbp->b_fname, nlen, namep) >= len) + return (ABORT); /* Name is too long. */ + + /* Return TRUE if the entry is a directory. */ + return ((lgetc(lp, 2) == 'd') ? TRUE : FALSE); +} + +#define NAME_FIELD 9 + +static int +d_warpdot(struct line *dotp, int *doto) +{ + char *tp = dotp->l_text; + int off = 0, field = 0, len; + + /* + * Find the byte offset to the (space-delimited) filename + * field in formatted ls output. + */ + len = llength(dotp); + while (off < len) { + if (tp[off++] == ' ') { + if (++field == NAME_FIELD) { + *doto = off; + return (TRUE); + } + /* Skip the space. */ + while (off < len && tp[off] == ' ') + off++; + } + } + /* We didn't find the field. */ + *doto = 0; + return (FALSE); +} + +static int +d_forwpage(int f, int n) +{ + forwpage(f | FFRAND, n); + return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); +} + +static int +d_backpage (int f, int n) +{ + backpage(f | FFRAND, n); + return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); +} + +static int +d_forwline (int f, int n) +{ + forwline(f | FFRAND, n); + return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); +} + +static int +d_backline (int f, int n) +{ + backline(f | FFRAND, n); + return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); +} + +/* + * XXX dname needs to have enough place to store an additional '/'. + */ +struct buffer * +dired_(char *dname) +{ + struct buffer *bp; + int len, i; + + if ((fopen(dname,"r")) == NULL) { + if (errno == EACCES) + ewprintf("Permission denied"); + return (NULL); + } + if ((dname = adjustname(dname, FALSE)) == NULL) { + ewprintf("Bad directory name"); + return (NULL); + } + /* this should not be done, instead adjustname() should get a flag */ + len = strlen(dname); + if (dname[len - 1] != '/') { + dname[len++] = '/'; + dname[len] = '\0'; + } + if ((bp = findbuffer(dname)) == NULL) { + ewprintf("Could not create buffer"); + return (NULL); + } + if (bclear(bp) != TRUE) + return (NULL); + bp->b_flag |= BFREADONLY; + + if ((d_exec(2, bp, NULL, "ls", "-al", dname, NULL)) != TRUE) + return (NULL); + + /* Find the line with ".." on it. */ + bp->b_dotp = bfirstlp(bp); + for (i = 0; i < bp->b_lines; i++) { + bp->b_dotp = lforw(bp->b_dotp); + if (d_warpdot(bp->b_dotp, &bp->b_doto) == FALSE) + continue; + if (strcmp(ltext(bp->b_dotp) + bp->b_doto, "..") == 0) + break; + } + + /* We want dot on the entry right after "..", if possible. */ + if (++i < bp->b_lines - 2) + bp->b_dotp = lforw(bp->b_dotp); + d_warpdot(bp->b_dotp, &bp->b_doto); + + (void)strlcpy(bp->b_fname, dname, sizeof(bp->b_fname)); + (void)strlcpy(bp->b_cwd, dname, sizeof(bp->b_cwd)); + if ((bp->b_modes[1] = name_mode("dired")) == NULL) { + bp->b_modes[0] = name_mode("fundamental"); + ewprintf("Could not find mode dired"); + return (NULL); + } + bp->b_nmodes = 1; + return (bp); +} diff --git a/mg/display.c b/mg/display.c new file mode 100644 index 0000000..ba7c580 --- /dev/null +++ b/mg/display.c @@ -0,0 +1,1064 @@ +/* $OpenBSD: display.c,v 1.37 2009/06/04 02:23:37 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * The functions in this file handle redisplay. The + * redisplay system knows almost nothing about the editing + * process; the editing functions do, however, set some + * hints to eliminate a lot of the grinding. There is more + * that can be done; the "vtputc" interface is a real + * pig. + */ +#include "def.h" +#include "kbd.h" + +#include + +/* + * You can change these back to the types + * implied by the name if you get tight for space. If you + * make both of them "int" you get better code on the VAX. + * They do nothing if this is not Gosling redisplay, except + * for change the size of a structure that isn't used. + * A bit of a cheat. + */ +#define XCHAR int +#define XSHORT int + +#ifdef STANDOUT_GLITCH +#include +#endif + +/* + * A video structure always holds + * an array of characters whose length is equal to + * the longest line possible. v_text is allocated + * dynamically to fit the screen width. + */ +struct video { + short v_hash; /* Hash code, for compares. */ + short v_flag; /* Flag word. */ + short v_color; /* Color of the line. */ + XSHORT v_cost; /* Cost of display. */ + char *v_text; /* The actual characters. */ +}; + +#define VFCHG 0x0001 /* Changed. */ +#define VFHBAD 0x0002 /* Hash and cost are bad. */ +#define VFEXT 0x0004 /* extended line (beond ncol) */ + +/* + * SCORE structures hold the optimal + * trace trajectory, and the cost of redisplay, when + * the dynamic programming redisplay code is used. + * If no fancy redisplay, this isn't used. The trace index + * fields can be "char", and the cost a "short", but + * this makes the code worse on the VAX. + */ +struct score { + XCHAR s_itrace; /* "i" index for track back. */ + XCHAR s_jtrace; /* "j" index for trace back. */ + XSHORT s_cost; /* Display cost. */ +}; + +void vtmove(int, int); +void vtputc(int); +void vtpute(int); +int vtputs(const char *); +void vteeol(void); +void updext(int, int); +void modeline(struct mgwin *); +void setscores(int, int); +void traceback(int, int, int, int); +void ucopy(struct video *, struct video *); +void uline(int, struct video *, struct video *); +void hash(struct video *); + + +int sgarbf = TRUE; /* TRUE if screen is garbage. */ +int vtrow = HUGE; /* Virtual cursor row. */ +int vtcol = HUGE; /* Virtual cursor column. */ +int tthue = CNONE; /* Current color. */ +int ttrow = HUGE; /* Physical cursor row. */ +int ttcol = HUGE; /* Physical cursor column. */ +int tttop = HUGE; /* Top of scroll region. */ +int ttbot = HUGE; /* Bottom of scroll region. */ +int lbound = 0; /* leftmost bound of the current */ + /* line being displayed */ + +struct video **vscreen; /* Edge vector, virtual. */ +struct video **pscreen; /* Edge vector, physical. */ +struct video *video; /* Actual screen data. */ +struct video blanks; /* Blank line image. */ + +/* + * This matrix is written as an array because + * we do funny things in the "setscores" routine, which + * is very compute intensive, to make the subscripts go away. + * It would be "SCORE score[NROW][NROW]" in old speak. + * Look at "setscores" to understand what is up. + */ +struct score *score; /* [NROW * NROW] */ + +#ifndef LINENOMODE +#define LINENOMODE TRUE +#endif /* !LINENOMODE */ +static int linenos = LINENOMODE; + +/* Is macro recording enabled? */ +extern int macrodef; +/* Is working directory global? */ +extern int globalwd; + +/* + * Since we don't have variables (we probably should) these are command + * processors for changing the values of mode flags. + */ +/* ARGSUSED */ +int +linenotoggle(int f, int n) +{ + if (f & FFARG) + linenos = n > 0; + else + linenos = !linenos; + + sgarbf = TRUE; + + return (TRUE); +} + +/* + * Reinit the display data structures, this is called when the terminal + * size changes. + */ +int +vtresize(int force, int newrow, int newcol) +{ + int i; + int rowchanged, colchanged; + static int first_run = 1; + struct video *vp; + + if (newrow < 1 || newcol < 1) + return (FALSE); + + rowchanged = (newrow != nrow); + colchanged = (newcol != ncol); + +#define TRYREALLOC(a, n) do { \ + void *tmp; \ + if ((tmp = realloc((a), (n))) == NULL) { \ + panic("out of memory in display code"); \ + } \ + (a) = tmp; \ + } while (0) + + /* No update needed */ + if (!first_run && !force && !rowchanged && !colchanged) + return (TRUE); + + if (first_run) + memset(&blanks, 0, sizeof(blanks)); + + if (rowchanged || first_run) { + int vidstart; + + /* + * This is not pretty. + */ + if (nrow == 0) + vidstart = 0; + else + vidstart = 2 * (nrow - 1); + + /* + * We're shrinking, free some internal data. + */ + if (newrow < nrow) { + for (i = 2 * (newrow - 1); i < 2 * (nrow - 1); i++) { + free(video[i].v_text); + video[i].v_text = NULL; + } + } + + TRYREALLOC(score, newrow * newrow * sizeof(struct score)); + TRYREALLOC(vscreen, (newrow - 1) * sizeof(struct video *)); + TRYREALLOC(pscreen, (newrow - 1) * sizeof(struct video *)); + TRYREALLOC(video, (2 * (newrow - 1)) * sizeof(struct video)); + + /* + * Zero-out the entries we just allocated. + */ + for (i = vidstart; i < 2 * (newrow - 1); i++) + memset(&video[i], 0, sizeof(struct video)); + + /* + * Reinitialize vscreen and pscreen arrays completely. + */ + vp = &video[0]; + for (i = 0; i < newrow - 1; ++i) { + vscreen[i] = vp; + ++vp; + pscreen[i] = vp; + ++vp; + } + } + if (rowchanged || colchanged || first_run) { + for (i = 0; i < 2 * (newrow - 1); i++) + TRYREALLOC(video[i].v_text, newcol * sizeof(char)); + TRYREALLOC(blanks.v_text, newcol * sizeof(char)); + } + + nrow = newrow; + ncol = newcol; + + if (ttrow > nrow) + ttrow = nrow; + if (ttcol > ncol) + ttcol = ncol; + + first_run = 0; + return (TRUE); +} + +#undef TRYREALLOC + +/* + * Initialize the data structures used + * by the display code. The edge vectors used + * to access the screens are set up. The operating + * system's terminal I/O channel is set up. Fill the + * "blanks" array with ASCII blanks. The rest is done + * at compile time. The original window is marked + * as needing full update, and the physical screen + * is marked as garbage, so all the right stuff happens + * on the first call to redisplay. + */ +void +vtinit(void) +{ + int i; + + ttopen(); + ttinit(); + + /* + * ttinit called ttresize(), which called vtresize(), so our data + * structures are setup correctly. + */ + + blanks.v_color = CTEXT; + for (i = 0; i < ncol; ++i) + blanks.v_text[i] = ' '; +} + +/* + * Tidy up the virtual display system + * in anticipation of a return back to the host + * operating system. Right now all we do is position + * the cursor to the last line, erase the line, and + * close the terminal channel. + */ +void +vttidy(void) +{ + ttcolor(CTEXT); + ttnowindow(); /* No scroll window. */ + ttmove(nrow - 1, 0); /* Echo line. */ + tteeol(); + tttidy(); + ttflush(); + ttclose(); +} + +/* + * Move the virtual cursor to an origin + * 0 spot on the virtual display screen. I could + * store the column as a character pointer to the spot + * on the line, which would make "vtputc" a little bit + * more efficient. No checking for errors. + */ +void +vtmove(int row, int col) +{ + vtrow = row; + vtcol = col; +} + +/* + * Write a character to the virtual display, + * dealing with long lines and the display of unprintable + * things like control characters. Also expand tabs every 8 + * columns. This code only puts printing characters into + * the virtual display image. Special care must be taken when + * expanding tabs. On a screen whose width is not a multiple + * of 8, it is possible for the virtual cursor to hit the + * right margin before the next tab stop is reached. This + * makes the tab code loop if you are not careful. + * Three guesses how we found this. + */ +void +vtputc(int c) +{ + struct video *vp; + + c &= 0xff; + + vp = vscreen[vtrow]; + if (vtcol >= ncol) + vp->v_text[ncol - 1] = '$'; + else if (c == '\t' +#ifdef NOTAB + && !(curbp->b_flag & BFNOTAB) +#endif + ) { + do { + vtputc(' '); + } while (vtcol < ncol && (vtcol & 0x07) != 0); + } else if (ISCTRL(c)) { + vtputc('^'); + vtputc(CCHR(c)); + } else if (isprint(c)) + vp->v_text[vtcol++] = c; + else { + char bf[5]; + + snprintf(bf, sizeof(bf), "\\%o", c); + vtputs(bf); + } +} + +/* + * Put a character to the virtual screen in an extended line. If we are not + * yet on left edge, don't print it yet. Check for overflow on the right + * margin. + */ +void +vtpute(int c) +{ + struct video *vp; + + c &= 0xff; + + vp = vscreen[vtrow]; + if (vtcol >= ncol) + vp->v_text[ncol - 1] = '$'; + else if (c == '\t' +#ifdef NOTAB + && !(curbp->b_flag & BFNOTAB) +#endif + ) { + do { + vtpute(' '); + } while (((vtcol + lbound) & 0x07) != 0 && vtcol < ncol); + } else if (ISCTRL(c) != FALSE) { + vtpute('^'); + vtpute(CCHR(c)); + } else { + if (vtcol >= 0) + vp->v_text[vtcol] = c; + ++vtcol; + } +} + +/* + * Erase from the end of the software cursor to the end of the line on which + * the software cursor is located. The display routines will decide if a + * hardware erase to end of line command should be used to display this. + */ +void +vteeol(void) +{ + struct video *vp; + + vp = vscreen[vtrow]; + while (vtcol < ncol) + vp->v_text[vtcol++] = ' '; +} + +/* + * Make sure that the display is + * right. This is a three part process. First, + * scan through all of the windows looking for dirty + * ones. Check the framing, and refresh the screen. + * Second, make sure that "currow" and "curcol" are + * correct for the current window. Third, make the + * virtual and physical screens the same. + */ +void +update(void) +{ + struct line *lp; + struct mgwin *wp; + struct video *vp1; + struct video *vp2; + int c, i, j; + int hflag; + int currow, curcol; + int offs, size; + + if (charswaiting()) + return; + if (sgarbf) { /* must update everything */ + wp = wheadp; + while (wp != NULL) { + wp->w_rflag |= WFMODE | WFFULL; + wp = wp->w_wndp; + } + } + if (linenos) { + wp = wheadp; + while (wp != NULL) { + wp->w_rflag |= WFMODE; + wp = wp->w_wndp; + } + } + hflag = FALSE; /* Not hard. */ + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + /* + * Nothing to be done. + */ + if (wp->w_rflag == 0) + continue; + + if ((wp->w_rflag & WFFRAME) == 0) { + lp = wp->w_linep; + for (i = 0; i < wp->w_ntrows; ++i) { + if (lp == wp->w_dotp) + goto out; + if (lp == wp->w_bufp->b_headp) + break; + lp = lforw(lp); + } + } + /* + * Put the middle-line in place. + */ + i = wp->w_frame; + if (i > 0) { + --i; + if (i >= wp->w_ntrows) + i = wp->w_ntrows - 1; + } else if (i < 0) { + i += wp->w_ntrows; + if (i < 0) + i = 0; + } else + i = wp->w_ntrows / 2; /* current center, no change */ + + /* + * Find the line. + */ + lp = wp->w_dotp; + while (i != 0 && lback(lp) != wp->w_bufp->b_headp) { + --i; + lp = lback(lp); + } + wp->w_linep = lp; + wp->w_rflag |= WFFULL; /* Force full. */ + out: + lp = wp->w_linep; /* Try reduced update. */ + i = wp->w_toprow; + if ((wp->w_rflag & ~WFMODE) == WFEDIT) { + while (lp != wp->w_dotp) { + ++i; + lp = lforw(lp); + } + vscreen[i]->v_color = CTEXT; + vscreen[i]->v_flag |= (VFCHG | VFHBAD); + vtmove(i, 0); + for (j = 0; j < llength(lp); ++j) + vtputc(lgetc(lp, j)); + vteeol(); + } else if ((wp->w_rflag & (WFEDIT | WFFULL)) != 0) { + hflag = TRUE; + while (i < wp->w_toprow + wp->w_ntrows) { + vscreen[i]->v_color = CTEXT; + vscreen[i]->v_flag |= (VFCHG | VFHBAD); + vtmove(i, 0); + if (lp != wp->w_bufp->b_headp) { + for (j = 0; j < llength(lp); ++j) + vtputc(lgetc(lp, j)); + lp = lforw(lp); + } + vteeol(); + ++i; + } + } + if ((wp->w_rflag & WFMODE) != 0) + modeline(wp); + wp->w_rflag = 0; + wp->w_frame = 0; + } + lp = curwp->w_linep; /* Cursor location. */ + currow = curwp->w_toprow; + while (lp != curwp->w_dotp) { + ++currow; + lp = lforw(lp); + } + curcol = 0; + i = 0; + while (i < curwp->w_doto) { + c = lgetc(lp, i++); + if (c == '\t' +#ifdef NOTAB + && !(curbp->b_flag & BFNOTAB) +#endif + ) { + curcol |= 0x07; + curcol++; + } else if (ISCTRL(c) != FALSE) + curcol += 2; + else if (isprint(c)) + curcol++; + else { + char bf[5]; + + snprintf(bf, sizeof(bf), "\\%o", c); + curcol += strlen(bf); + } + } + if (curcol >= ncol - 1) { /* extended line. */ + /* flag we are extended and changed */ + vscreen[currow]->v_flag |= VFEXT | VFCHG; + updext(currow, curcol); /* and output extended line */ + } else + lbound = 0; /* not extended line */ + + /* + * Make sure no lines need to be de-extended because the cursor is no + * longer on them. + */ + wp = wheadp; + while (wp != NULL) { + lp = wp->w_linep; + i = wp->w_toprow; + while (i < wp->w_toprow + wp->w_ntrows) { + if (vscreen[i]->v_flag & VFEXT) { + /* always flag extended lines as changed */ + vscreen[i]->v_flag |= VFCHG; + if ((wp != curwp) || (lp != wp->w_dotp) || + (curcol < ncol - 1)) { + vtmove(i, 0); + for (j = 0; j < llength(lp); ++j) + vtputc(lgetc(lp, j)); + vteeol(); + /* this line no longer is extended */ + vscreen[i]->v_flag &= ~VFEXT; + } + } + lp = lforw(lp); + ++i; + } + /* if garbaged then fix up mode lines */ + if (sgarbf != FALSE) + vscreen[i]->v_flag |= VFCHG; + /* and onward to the next window */ + wp = wp->w_wndp; + } + + if (sgarbf != FALSE) { /* Screen is garbage. */ + sgarbf = FALSE; /* Erase-page clears. */ + epresf = FALSE; /* The message area. */ + tttop = HUGE; /* Forget where you set. */ + ttbot = HUGE; /* scroll region. */ + tthue = CNONE; /* Color unknown. */ + ttmove(0, 0); + tteeop(); + for (i = 0; i < nrow - 1; ++i) { + uline(i, vscreen[i], &blanks); + ucopy(vscreen[i], pscreen[i]); + } + ttmove(currow, curcol - lbound); + ttflush(); + return; + } + if (hflag != FALSE) { /* Hard update? */ + for (i = 0; i < nrow - 1; ++i) {/* Compute hash data. */ + hash(vscreen[i]); + hash(pscreen[i]); + } + offs = 0; /* Get top match. */ + while (offs != nrow - 1) { + vp1 = vscreen[offs]; + vp2 = pscreen[offs]; + if (vp1->v_color != vp2->v_color + || vp1->v_hash != vp2->v_hash) + break; + uline(offs, vp1, vp2); + ucopy(vp1, vp2); + ++offs; + } + if (offs == nrow - 1) { /* Might get it all. */ + ttmove(currow, curcol - lbound); + ttflush(); + return; + } + size = nrow - 1; /* Get bottom match. */ + while (size != offs) { + vp1 = vscreen[size - 1]; + vp2 = pscreen[size - 1]; + if (vp1->v_color != vp2->v_color + || vp1->v_hash != vp2->v_hash) + break; + uline(size - 1, vp1, vp2); + ucopy(vp1, vp2); + --size; + } + if ((size -= offs) == 0) /* Get screen size. */ + panic("Illegal screen size in update"); + setscores(offs, size); /* Do hard update. */ + traceback(offs, size, size, size); + for (i = 0; i < size; ++i) + ucopy(vscreen[offs + i], pscreen[offs + i]); + ttmove(currow, curcol - lbound); + ttflush(); + return; + } + for (i = 0; i < nrow - 1; ++i) { /* Easy update. */ + vp1 = vscreen[i]; + vp2 = pscreen[i]; + if ((vp1->v_flag & VFCHG) != 0) { + uline(i, vp1, vp2); + ucopy(vp1, vp2); + } + } + ttmove(currow, curcol - lbound); + ttflush(); +} + +/* + * Update a saved copy of a line, + * kept in a video structure. The "vvp" is + * the one in the "vscreen". The "pvp" is the one + * in the "pscreen". This is called to make the + * virtual and physical screens the same when + * display has done an update. + */ +void +ucopy(struct video *vvp, struct video *pvp) +{ + vvp->v_flag &= ~VFCHG; /* Changes done. */ + pvp->v_flag = vvp->v_flag; /* Update model. */ + pvp->v_hash = vvp->v_hash; + pvp->v_cost = vvp->v_cost; + pvp->v_color = vvp->v_color; + bcopy(vvp->v_text, pvp->v_text, ncol); +} + +/* + * updext: update the extended line which the cursor is currently on at a + * column greater than the terminal width. The line will be scrolled right or + * left to let the user see where the cursor is. + */ +void +updext(int currow, int curcol) +{ + struct line *lp; /* pointer to current line */ + int j; /* index into line */ + + if (ncol < 2) + return; + + /* + * calculate what column the left bound should be + * (force cursor into middle half of screen) + */ + lbound = curcol - (curcol % (ncol >> 1)) - (ncol >> 2); + + /* + * scan through the line outputing characters to the virtual screen + * once we reach the left edge + */ + vtmove(currow, -lbound); /* start scanning offscreen */ + lp = curwp->w_dotp; /* line to output */ + for (j = 0; j < llength(lp); ++j) /* until the end-of-line */ + vtpute(lgetc(lp, j)); + vteeol(); /* truncate the virtual line */ + vscreen[currow]->v_text[0] = '$'; /* and put a '$' in column 1 */ +} + +/* + * Update a single line. This routine only + * uses basic functionality (no insert and delete character, + * but erase to end of line). The "vvp" points at the video + * structure for the line on the virtual screen, and the "pvp" + * is the same for the physical screen. Avoid erase to end of + * line when updating CMODE color lines, because of the way that + * reverse video works on most terminals. + */ +void +uline(int row, struct video *vvp, struct video *pvp) +{ + char *cp1; + char *cp2; + char *cp3; + char *cp4; + char *cp5; + int nbflag; + + if (vvp->v_color != pvp->v_color) { /* Wrong color, do a */ + ttmove(row, 0); /* full redraw. */ +#ifdef STANDOUT_GLITCH + if (pvp->v_color != CTEXT && magic_cookie_glitch >= 0) + tteeol(); +#endif + ttcolor(vvp->v_color); +#ifdef STANDOUT_GLITCH + cp1 = &vvp->v_text[magic_cookie_glitch > 0 ? magic_cookie_glitch : 0]; + /* + * The odd code for magic_cookie_glitch==0 is to avoid + * putting the invisible glitch character on the next line. + * (Hazeltine executive 80 model 30) + */ + cp2 = &vvp->v_text[ncol - (magic_cookie_glitch >= 0 ? + (magic_cookie_glitch != 0 ? magic_cookie_glitch : 1) : 0)]; +#else + cp1 = &vvp->v_text[0]; + cp2 = &vvp->v_text[ncol]; +#endif + while (cp1 != cp2) { + ttputc(*cp1++); + ++ttcol; + } +#ifndef MOVE_STANDOUT + ttcolor(CTEXT); +#endif + return; + } + cp1 = &vvp->v_text[0]; /* Compute left match. */ + cp2 = &pvp->v_text[0]; + while (cp1 != &vvp->v_text[ncol] && cp1[0] == cp2[0]) { + ++cp1; + ++cp2; + } + if (cp1 == &vvp->v_text[ncol]) /* All equal. */ + return; + nbflag = FALSE; + cp3 = &vvp->v_text[ncol]; /* Compute right match. */ + cp4 = &pvp->v_text[ncol]; + while (cp3[-1] == cp4[-1]) { + --cp3; + --cp4; + if (cp3[0] != ' ') /* Note non-blanks in */ + nbflag = TRUE; /* the right match. */ + } + cp5 = cp3; /* Is erase good? */ + if (nbflag == FALSE && vvp->v_color == CTEXT) { + while (cp5 != cp1 && cp5[-1] == ' ') + --cp5; + /* Alcyon hack */ + if ((int) (cp3 - cp5) <= tceeol) + cp5 = cp3; + } + /* Alcyon hack */ + ttmove(row, (int) (cp1 - &vvp->v_text[0])); +#ifdef STANDOUT_GLITCH + if (vvp->v_color != CTEXT && magic_cookie_glitch > 0) { + if (cp1 < &vvp->v_text[magic_cookie_glitch]) + cp1 = &vvp->v_text[magic_cookie_glitch]; + if (cp5 > &vvp->v_text[ncol - magic_cookie_glitch]) + cp5 = &vvp->v_text[ncol - magic_cookie_glitch]; + } else if (magic_cookie_glitch < 0) +#endif + ttcolor(vvp->v_color); + while (cp1 != cp5) { + ttputc(*cp1++); + ++ttcol; + } + if (cp5 != cp3) /* Do erase. */ + tteeol(); +} + +/* + * Redisplay the mode line for the window pointed to by the "wp". + * This is the only routine that has any idea of how the mode line is + * formatted. You can change the modeline format by hacking at this + * routine. Called by "update" any time there is a dirty window. Note + * that if STANDOUT_GLITCH is defined, first and last magic_cookie_glitch + * characters may never be seen. + */ +void +modeline(struct mgwin *wp) +{ + int n, md; + struct buffer *bp; + char sl[21]; /* Overkill. Space for 2^64 in base 10. */ + int len; + + n = wp->w_toprow + wp->w_ntrows; /* Location. */ + vscreen[n]->v_color = CMODE; /* Mode line color. */ + vscreen[n]->v_flag |= (VFCHG | VFHBAD); /* Recompute, display. */ + vtmove(n, 0); /* Seek to right line. */ + bp = wp->w_bufp; + vtputc('-'); + vtputc('-'); + if ((bp->b_flag & BFREADONLY) != 0) { + vtputc('%'); + if ((bp->b_flag & BFCHG) != 0) + vtputc('*'); + else + vtputc('%'); + } else if ((bp->b_flag & BFCHG) != 0) { /* "*" if changed. */ + vtputc('*'); + vtputc('*'); + } else { + vtputc('-'); + vtputc('-'); + } + vtputc('-'); + n = 5; + n += vtputs("Mg: "); + if (bp->b_bname[0] != '\0') + n += vtputs(&(bp->b_bname[0])); + while (n < 42) { /* Pad out with blanks. */ + vtputc(' '); + ++n; + } + vtputc('('); + ++n; + for (md = 0; ; ) { + n += vtputs(bp->b_modes[md]->p_name); + if (++md > bp->b_nmodes) + break; + vtputc('-'); + ++n; + } + /* XXX These should eventually move to a real mode */ + if (macrodef == TRUE) + n += vtputs("-def"); + if (globalwd == TRUE) + n += vtputs("-gwd"); + vtputc(')'); + ++n; + + if (linenos) { + len = snprintf(sl, sizeof(sl), "--L%d--C%d", wp->w_dotline, + getcolpos()); + if (len < sizeof(sl) && len != -1) + n += vtputs(sl); + } + + while (n < ncol) { /* Pad out. */ + vtputc('-'); + ++n; + } +} + +/* + * Output a string to the mode line, report how long it was. + */ +int +vtputs(const char *s) +{ + int n = 0; + + while (*s != '\0') { + vtputc(*s++); + ++n; + } + return (n); +} + +/* + * Compute the hash code for the line pointed to by the "vp". + * Recompute it if necessary. Also set the approximate redisplay + * cost. The validity of the hash code is marked by a flag bit. + * The cost understand the advantages of erase to end of line. + * Tuned for the VAX by Bob McNamara; better than it used to be on + * just about any machine. + */ +void +hash(struct video *vp) +{ + int i, n; + char *s; + + if ((vp->v_flag & VFHBAD) != 0) { /* Hash bad. */ + s = &vp->v_text[ncol - 1]; + for (i = ncol; i != 0; --i, --s) + if (*s != ' ') + break; + n = ncol - i; /* Erase cheaper? */ + if (n > tceeol) + n = tceeol; + vp->v_cost = i + n; /* Bytes + blanks. */ + for (n = 0; i != 0; --i, --s) + n = (n << 5) + n + *s; + vp->v_hash = n; /* Hash code. */ + vp->v_flag &= ~VFHBAD; /* Flag as all done. */ + } +} + +/* + * Compute the Insert-Delete + * cost matrix. The dynamic programming algorithm + * described by James Gosling is used. This code assumes + * that the line above the echo line is the last line involved + * in the scroll region. This is easy to arrange on the VT100 + * because of the scrolling region. The "offs" is the origin 0 + * offset of the first row in the virtual/physical screen that + * is being updated; the "size" is the length of the chunk of + * screen being updated. For a full screen update, use offs=0 + * and size=nrow-1. + * + * Older versions of this code implemented the score matrix by + * a two dimensional array of SCORE nodes. This put all kinds of + * multiply instructions in the code! This version is written to + * use a linear array and pointers, and contains no multiplication + * at all. The code has been carefully looked at on the VAX, with + * only marginal checking on other machines for efficiency. In + * fact, this has been tuned twice! Bob McNamara tuned it even + * more for the VAX, which is a big issue for him because of + * the 66 line X displays. + * + * On some machines, replacing the "for (i=1; i<=size; ++i)" with + * i = 1; do { } while (++i <=size)" will make the code quite a + * bit better; but it looks ugly. + */ +void +setscores(int offs, int size) +{ + struct score *sp; + struct score *sp1; + struct video **vp, **pp; + struct video **vbase, **pbase; + int tempcost; + int bestcost; + int j, i; + + vbase = &vscreen[offs - 1]; /* By hand CSE's. */ + pbase = &pscreen[offs - 1]; + score[0].s_itrace = 0; /* [0, 0] */ + score[0].s_jtrace = 0; + score[0].s_cost = 0; + sp = &score[1]; /* Row 0, inserts. */ + tempcost = 0; + vp = &vbase[1]; + for (j = 1; j <= size; ++j) { + sp->s_itrace = 0; + sp->s_jtrace = j - 1; + tempcost += tcinsl; + tempcost += (*vp)->v_cost; + sp->s_cost = tempcost; + ++vp; + ++sp; + } + sp = &score[nrow]; /* Column 0, deletes. */ + tempcost = 0; + for (i = 1; i <= size; ++i) { + sp->s_itrace = i - 1; + sp->s_jtrace = 0; + tempcost += tcdell; + sp->s_cost = tempcost; + sp += nrow; + } + sp1 = &score[nrow + 1]; /* [1, 1]. */ + pp = &pbase[1]; + for (i = 1; i <= size; ++i) { + sp = sp1; + vp = &vbase[1]; + for (j = 1; j <= size; ++j) { + sp->s_itrace = i - 1; + sp->s_jtrace = j; + bestcost = (sp - nrow)->s_cost; + if (j != size) /* Cd(A[i])=0 @ Dis. */ + bestcost += tcdell; + tempcost = (sp - 1)->s_cost; + tempcost += (*vp)->v_cost; + if (i != size) /* Ci(B[j])=0 @ Dsj. */ + tempcost += tcinsl; + if (tempcost < bestcost) { + sp->s_itrace = i; + sp->s_jtrace = j - 1; + bestcost = tempcost; + } + tempcost = (sp - nrow - 1)->s_cost; + if ((*pp)->v_color != (*vp)->v_color + || (*pp)->v_hash != (*vp)->v_hash) + tempcost += (*vp)->v_cost; + if (tempcost < bestcost) { + sp->s_itrace = i - 1; + sp->s_jtrace = j - 1; + bestcost = tempcost; + } + sp->s_cost = bestcost; + ++sp; /* Next column. */ + ++vp; + } + ++pp; + sp1 += nrow; /* Next row. */ + } +} + +/* + * Trace back through the dynamic programming cost + * matrix, and update the screen using an optimal sequence + * of redraws, insert lines, and delete lines. The "offs" is + * the origin 0 offset of the chunk of the screen we are about to + * update. The "i" and "j" are always started in the lower right + * corner of the matrix, and imply the size of the screen. + * A full screen traceback is called with offs=0 and i=j=nrow-1. + * There is some do-it-yourself double subscripting here, + * which is acceptable because this routine is much less compute + * intensive then the code that builds the score matrix! + */ +void +traceback(int offs, int size, int i, int j) +{ + int itrace, jtrace; + int k; + int ninsl, ndraw, ndell; + + if (i == 0 && j == 0) /* End of update. */ + return; + itrace = score[(nrow * i) + j].s_itrace; + jtrace = score[(nrow * i) + j].s_jtrace; + if (itrace == i) { /* [i, j-1] */ + ninsl = 0; /* Collect inserts. */ + if (i != size) + ninsl = 1; + ndraw = 1; + while (itrace != 0 || jtrace != 0) { + if (score[(nrow * itrace) + jtrace].s_itrace != itrace) + break; + jtrace = score[(nrow * itrace) + jtrace].s_jtrace; + if (i != size) + ++ninsl; + ++ndraw; + } + traceback(offs, size, itrace, jtrace); + if (ninsl != 0) { + ttcolor(CTEXT); + ttinsl(offs + j - ninsl, offs + size - 1, ninsl); + } + do { /* B[j], A[j] blank. */ + k = offs + j - ndraw; + uline(k, vscreen[k], &blanks); + } while (--ndraw); + return; + } + if (jtrace == j) { /* [i-1, j] */ + ndell = 0; /* Collect deletes. */ + if (j != size) + ndell = 1; + while (itrace != 0 || jtrace != 0) { + if (score[(nrow * itrace) + jtrace].s_jtrace != jtrace) + break; + itrace = score[(nrow * itrace) + jtrace].s_itrace; + if (j != size) + ++ndell; + } + if (ndell != 0) { + ttcolor(CTEXT); + ttdell(offs + i - ndell, offs + size - 1, ndell); + } + traceback(offs, size, itrace, jtrace); + return; + } + traceback(offs, size, itrace, jtrace); + k = offs + j - 1; + uline(k, vscreen[k], pscreen[offs + i - 1]); +} diff --git a/mg/echo.c b/mg/echo.c new file mode 100644 index 0000000..ce173f6 --- /dev/null +++ b/mg/echo.c @@ -0,0 +1,964 @@ +/* $OpenBSD: echo.c,v 1.50 2012/04/12 04:47:59 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Echo line reading and writing. + * + * Common routines for reading and writing characters in the echo line area + * of the display screen. Used by the entire known universe. + */ + +#include "def.h" +#include "key.h" +#include "macro.h" + +#include "funmap.h" + +#include +#include + +static char *veread(const char *, char *, size_t, int, va_list); +static int complt(int, int, char *, size_t, int, int *); +static int complt_list(int, char *, int); +static void eformat(const char *, va_list); +static void eputi(int, int); +static void eputl(long, int); +static void eputs(const char *); +static void eputc(char); +static struct list *copy_list(struct list *); + +int epresf = FALSE; /* stuff in echo line flag */ + +/* + * Erase the echo line. + */ +void +eerase(void) +{ + ttcolor(CTEXT); + ttmove(nrow - 1, 0); + tteeol(); + ttflush(); + epresf = FALSE; +} + +/* + * Ask a "yes" or "no" question. Return ABORT if the user answers the + * question with the abort ("^G") character. Return FALSE for "no" and + * TRUE for "yes". No formatting services are available. No newline + * required. + */ +int +eyorn(const char *sp) +{ + int s; + + if (inmacro) + return (TRUE); + + ewprintf("%s? (y or n) ", sp); + for (;;) { + s = getkey(FALSE); + if (s == 'y' || s == 'Y' || s == ' ') + return (TRUE); + if (s == 'n' || s == 'N' || s == CCHR('M')) + return (FALSE); + if (s == CCHR('G')) + return (ctrlg(FFRAND, 1)); + ewprintf("Please answer y or n. %s? (y or n) ", sp); + } + /* NOTREACHED */ +} + +/* + * Like eyorn, but for more important questions. User must type all of + * "yes" or "no" and the trailing newline. + */ +int +eyesno(const char *sp) +{ + char buf[64], *rep; + + if (inmacro) + return (TRUE); + + rep = eread("%s? (yes or no) ", buf, sizeof(buf), + EFNUL | EFNEW | EFCR, sp); + for (;;) { + if (rep == NULL) + return (ABORT); + if (rep[0] != '\0') { + if (macrodef) { + struct line *lp = maclcur; + + maclcur = lp->l_bp; + maclcur->l_fp = lp->l_fp; + free(lp); + } + if ((rep[0] == 'y' || rep[0] == 'Y') && + (rep[1] == 'e' || rep[1] == 'E') && + (rep[2] == 's' || rep[2] == 'S') && + (rep[3] == '\0')) + return (TRUE); + if ((rep[0] == 'n' || rep[0] == 'N') && + (rep[1] == 'o' || rep[0] == 'O') && + (rep[2] == '\0')) + return (FALSE); + } + rep = eread("Please answer yes or no. %s? (yes or no) ", + buf, sizeof(buf), EFNUL | EFNEW | EFCR, sp); + } + /* NOTREACHED */ +} + +/* + * This is the general "read input from the echo line" routine. The basic + * idea is that the prompt string "prompt" is written to the echo line, and + * a one line reply is read back into the supplied "buf" (with maximum + * length "len"). + * XXX: When checking for an empty return value, always check rep, *not* buf + * as buf may be freed in pathological cases. + */ +/* VARARGS */ +char * +eread(const char *fmt, char *buf, size_t nbuf, int flag, ...) +{ + va_list ap; + char *rep; + + va_start(ap, flag); + rep = veread(fmt, buf, nbuf, flag, ap); + va_end(ap); + return (rep); +} + +static char * +veread(const char *fp, char *buf, size_t nbuf, int flag, va_list ap) +{ + int dynbuf = (buf == NULL); + int cpos, epos; /* cursor, end position in buf */ + int c, i, y; + int cplflag = FALSE; /* display completion list */ + int cwin = FALSE; /* completion list created */ + int mr = 0; /* match left arrow */ + int ml = 0; /* match right arrow */ + int esc = 0; /* position in esc pattern */ + struct buffer *bp; /* completion list buffer */ + struct mgwin *wp; /* window for compl list */ + int match; /* esc match found */ + int cc, rr; /* saved ttcol, ttrow */ + char *ret; /* return value */ + + static char emptyval[] = ""; /* XXX hackish way to return err msg*/ + + if (inmacro) { + if (dynbuf) { + if ((buf = malloc(maclcur->l_used + 1)) == NULL) + return (NULL); + } else if (maclcur->l_used >= nbuf) + return (NULL); + bcopy(maclcur->l_text, buf, maclcur->l_used); + buf[maclcur->l_used] = '\0'; + maclcur = maclcur->l_fp; + return (buf); + } + epos = cpos = 0; + ml = mr = esc = 0; + cplflag = FALSE; + + if ((flag & EFNEW) != 0 || ttrow != nrow - 1) { + ttcolor(CTEXT); + ttmove(nrow - 1, 0); + epresf = TRUE; + } else + eputc(' '); + eformat(fp, ap); + if ((flag & EFDEF) != 0) { + if (buf == NULL) + return (NULL); + eputs(buf); + epos = cpos += strlen(buf); + } + tteeol(); + ttflush(); + for (;;) { + c = getkey(FALSE); + if ((flag & EFAUTO) != 0 && (c == ' ' || c == CCHR('I'))) { + if (cplflag == TRUE) { + complt_list(flag, buf, cpos); + cwin = TRUE; + } else if (complt(flag, c, buf, nbuf, epos, &i) == TRUE) { + cplflag = TRUE; + epos += i; + cpos = epos; + } + continue; + } + cplflag = FALSE; + + if (esc > 0) { /* ESC sequence started */ + match = 0; + if (ml == esc && key_left[ml] && c == key_left[ml]) { + match++; + if (key_left[++ml] == '\0') { + c = CCHR('B'); + esc = 0; + } + } + if (mr == esc && key_right[mr] && c == key_right[mr]) { + match++; + if (key_right[++mr] == '\0') { + c = CCHR('F'); + esc = 0; + } + } + if (match == 0) { + esc = 0; + continue; + /* hack. how do we know esc pattern is done? */ + } + if (esc > 0) { + esc++; + continue; + } + } + switch (c) { + case CCHR('A'): /* start of line */ + while (cpos > 0) { + if (ISCTRL(buf[--cpos]) != FALSE) { + ttputc('\b'); + --ttcol; + } + ttputc('\b'); + --ttcol; + } + ttflush(); + break; + case CCHR('D'): + if (cpos != epos) { + tteeol(); + y = buf[cpos]; + epos--; + rr = ttrow; + cc = ttcol; + for (i = cpos; i < epos; i++) { + buf[i] = buf[i + 1]; + eputc(buf[i]); + } + ttmove(rr, cc); + ttflush(); + } + break; + case CCHR('E'): /* end of line */ + while (cpos < epos) { + eputc(buf[cpos++]); + } + ttflush(); + break; + case CCHR('B'): /* back */ + if (cpos > 0) { + if (ISCTRL(buf[--cpos]) != FALSE) { + ttputc('\b'); + --ttcol; + } + ttputc('\b'); + --ttcol; + ttflush(); + } + break; + case CCHR('F'): /* forw */ + if (cpos < epos) { + eputc(buf[cpos++]); + ttflush(); + } + break; + case CCHR('Y'): /* yank from kill buffer */ + i = 0; + while ((y = kremove(i++)) >= 0 && y != '\n') { + int t; + if (dynbuf && epos + 1 >= nbuf) { + void *newp; + size_t newsize = epos + epos + 16; + if ((newp = realloc(buf, newsize)) + == NULL) + goto memfail; + buf = newp; + nbuf = newsize; + } + if (!dynbuf && epos + 1 >= nbuf) { + ewprintf("Line too long"); + return (emptyval); + } + for (t = epos; t > cpos; t--) + buf[t] = buf[t - 1]; + buf[cpos++] = (char)y; + epos++; + eputc((char)y); + cc = ttcol; + rr = ttrow; + for (t = cpos; t < epos; t++) + eputc(buf[t]); + ttmove(rr, cc); + } + ttflush(); + break; + case CCHR('K'): /* copy here-EOL to kill buffer */ + kdelete(); + for (i = cpos; i < epos; i++) + kinsert(buf[i], KFORW); + tteeol(); + epos = cpos; + ttflush(); + break; + case CCHR('['): + ml = mr = esc = 1; + break; + case CCHR('J'): + c = CCHR('M'); + /* FALLTHROUGH */ + case CCHR('M'): /* return, done */ + /* if there's nothing in the minibuffer, abort */ + if (epos == 0 && !(flag & EFNUL)) { + (void)ctrlg(FFRAND, 0); + ttflush(); + return (NULL); + } + if ((flag & EFFUNC) != 0) { + if (complt(flag, c, buf, nbuf, epos, &i) + == FALSE) + continue; + if (i > 0) + epos += i; + } + buf[epos] = '\0'; + if ((flag & EFCR) != 0) { + ttputc(CCHR('M')); + ttflush(); + } + if (macrodef) { + struct line *lp; + + if ((lp = lalloc(cpos)) == NULL) + goto memfail; + lp->l_fp = maclcur->l_fp; + maclcur->l_fp = lp; + lp->l_bp = maclcur; + maclcur = lp; + bcopy(buf, lp->l_text, cpos); + } + ret = buf; + goto done; + case CCHR('G'): /* bell, abort */ + eputc(CCHR('G')); + (void)ctrlg(FFRAND, 0); + ttflush(); + ret = NULL; + goto done; + case CCHR('H'): /* rubout, erase */ + case CCHR('?'): + if (cpos != 0) { + y = buf[--cpos]; + epos--; + ttputc('\b'); + ttcol--; + if (ISCTRL(y) != FALSE) { + ttputc('\b'); + ttcol--; + } + rr = ttrow; + cc = ttcol; + for (i = cpos; i < epos; i++) { + buf[i] = buf[i + 1]; + eputc(buf[i]); + } + ttputc(' '); + if (ISCTRL(y) != FALSE) { + ttputc(' '); + ttputc('\b'); + } + ttputc('\b'); + ttmove(rr, cc); + ttflush(); + } + break; + case CCHR('X'): /* kill line */ + case CCHR('U'): + while (cpos != 0) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + if (ISCTRL(buf[--cpos]) != FALSE) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + } + epos--; + } + ttflush(); + break; + case CCHR('W'): /* kill to beginning of word */ + while ((cpos > 0) && !ISWORD(buf[cpos - 1])) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + if (ISCTRL(buf[--cpos]) != FALSE) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + } + epos--; + } + while ((cpos > 0) && ISWORD(buf[cpos - 1])) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + if (ISCTRL(buf[--cpos]) != FALSE) { + ttputc('\b'); + ttputc(' '); + ttputc('\b'); + --ttcol; + } + epos--; + } + ttflush(); + break; + case CCHR('\\'): + case CCHR('Q'): /* quote next */ + c = getkey(FALSE); + /* FALLTHROUGH */ + default: + if (dynbuf && epos + 1 >= nbuf) { + void *newp; + size_t newsize = epos + epos + 16; + if ((newp = realloc(buf, newsize)) == NULL) + goto memfail; + buf = newp; + nbuf = newsize; + } + if (!dynbuf && epos + 1 >= nbuf) { + ewprintf("Line too long"); + return (emptyval); + } + for (i = epos; i > cpos; i--) + buf[i] = buf[i - 1]; + buf[cpos++] = (char)c; + epos++; + eputc((char)c); + cc = ttcol; + rr = ttrow; + for (i = cpos; i < epos; i++) + eputc(buf[i]); + ttmove(rr, cc); + ttflush(); + } + } +done: + if (cwin == TRUE) { + /* blow away cpltion window */ + bp = bfind("*Completions*", TRUE); + if ((wp = popbuf(bp, WEPHEM)) != NULL) { + if (wp->w_flag & WEPHEM) { + curwp = wp; + delwind(FFRAND, 1); + } else { + killbuffer(bp); + } + } + } + return (ret); +memfail: + if (dynbuf && buf) + free(buf); + ewprintf("Out of memory"); + return (emptyval); +} + +/* + * Do completion on a list of objects. + * c is SPACE, TAB, or CR + * return TRUE if matched (or partially matched) + * FALSE is result is ambiguous, + * ABORT on error. + */ +static int +complt(int flags, int c, char *buf, size_t nbuf, int cpos, int *nx) +{ + struct list *lh, *lh2; + struct list *wholelist = NULL; + int i, nxtra, nhits, bxtra, msglen, nshown; + int wflag = FALSE; + char *msg; + + lh = lh2 = NULL; + + if ((flags & EFFUNC) != 0) { + buf[cpos] = '\0'; + wholelist = lh = complete_function_list(buf); + } else if ((flags & EFBUF) != 0) { + lh = &(bheadp->b_list); + } else if ((flags & EFFILE) != 0) { + buf[cpos] = '\0'; + wholelist = lh = make_file_list(buf); + } else + panic("broken complt call: flags"); + + if (c == ' ') + wflag = TRUE; + else if (c != '\t' && c != CCHR('M')) + panic("broken complt call: c"); + + nhits = 0; + nxtra = HUGE; + + for (; lh != NULL; lh = lh->l_next) { + if (memcmp(buf, lh->l_name, cpos) != 0) + continue; + if (nhits == 0) + lh2 = lh; + ++nhits; + if (lh->l_name[cpos] == '\0') + nxtra = -1; /* exact match */ + else { + bxtra = getxtra(lh, lh2, cpos, wflag); + if (bxtra < nxtra) + nxtra = bxtra; + lh2 = lh; + } + } + if (nhits == 0) + msg = " [No match]"; + else if (nhits > 1 && nxtra == 0) + msg = " [Ambiguous. Ctrl-G to cancel]"; + else { + /* + * Being lazy - ought to check length, but all things + * autocompleted have known types/lengths. + */ + if (nxtra < 0 && nhits > 1 && c == ' ') + nxtra = 1; /* ??? */ + for (i = 0; i < nxtra && cpos < nbuf; ++i) { + buf[cpos] = lh2->l_name[cpos]; + eputc(buf[cpos++]); + } + /* XXX should grow nbuf */ + ttflush(); + free_file_list(wholelist); + *nx = nxtra; + if (nxtra < 0 && c != CCHR('M')) /* exact */ + *nx = 0; + return (TRUE); + } + + /* + * wholelist is NULL if we are doing buffers. Want to free lists + * that were created for us, but not the buffer list! + */ + free_file_list(wholelist); + + /* Set up backspaces, etc., being mindful of echo line limit. */ + msglen = strlen(msg); + nshown = (ttcol + msglen + 2 > ncol) ? + ncol - ttcol - 2 : msglen; + eputs(msg); + ttcol -= (i = nshown); /* update ttcol! */ + while (i--) /* move back before msg */ + ttputc('\b'); + ttflush(); /* display to user */ + i = nshown; + while (i--) /* blank out on next flush */ + eputc(' '); + ttcol -= (i = nshown); /* update ttcol on BS's */ + while (i--) + ttputc('\b'); /* update ttcol again! */ + *nx = nxtra; + return ((nhits > 0) ? TRUE : FALSE); +} + +/* + * Do completion on a list of objects, listing instead of completing. + */ +static int +complt_list(int flags, char *buf, int cpos) +{ + struct list *lh, *lh2, *lh3; + struct list *wholelist = NULL; + struct buffer *bp; + int i, maxwidth, width; + int preflen = 0; + int oldrow = ttrow; + int oldcol = ttcol; + int oldhue = tthue; + char *linebuf; + size_t linesize, len; + char *cp; + + lh = NULL; + + ttflush(); + + /* The results are put into a completion buffer. */ + bp = bfind("*Completions*", TRUE); + if (bclear(bp) == FALSE) + return (FALSE); + + /* + * First get the list of objects. This list may contain only + * the ones that complete what has been typed, or may be the + * whole list of all objects of this type. They are filtered + * later in any case. Set wholelist if the list has been + * cons'ed up just for us, so we can free it later. We have + * to copy the buffer list for this function even though we + * didn't for complt. The sorting code does destructive + * changes to the list, which we don't want to happen to the + * main buffer list! + */ + if ((flags & EFBUF) != 0) + wholelist = lh = copy_list(&(bheadp->b_list)); + else if ((flags & EFFUNC) != 0) { + buf[cpos] = '\0'; + wholelist = lh = complete_function_list(buf); + } else if ((flags & EFFILE) != 0) { + buf[cpos] = '\0'; + wholelist = lh = make_file_list(buf); + /* + * We don't want to display stuff up to the / for file + * names preflen is the list of a prefix of what the + * user typed that should not be displayed. + */ + cp = strrchr(buf, '/'); + if (cp) + preflen = cp - buf + 1; + } else + panic("broken complt call: flags"); + + /* + * Sort the list, since users expect to see it in alphabetic + * order. + */ + lh2 = lh; + while (lh2 != NULL) { + lh3 = lh2->l_next; + while (lh3 != NULL) { + if (strcmp(lh2->l_name, lh3->l_name) > 0) { + cp = lh2->l_name; + lh2->l_name = lh3->l_name; + lh3->l_name = cp; + } + lh3 = lh3->l_next; + } + lh2 = lh2->l_next; + } + + /* + * First find max width of object to be displayed, so we can + * put several on a line. + */ + maxwidth = 0; + lh2 = lh; + while (lh2 != NULL) { + for (i = 0; i < cpos; ++i) { + if (buf[i] != lh2->l_name[i]) + break; + } + if (i == cpos) { + width = strlen(lh2->l_name); + if (width > maxwidth) + maxwidth = width; + } + lh2 = lh2->l_next; + } + maxwidth += 1 - preflen; + + /* + * Now do the display. Objects are written into linebuf until + * it fills, and then put into the help buffer. + */ + linesize = MAX(ncol, maxwidth) + 1; + if ((linebuf = malloc(linesize)) == NULL) + return (FALSE); + width = 0; + + /* + * We're going to strlcat() into the buffer, so it has to be + * NUL terminated. + */ + linebuf[0] = '\0'; + for (lh2 = lh; lh2 != NULL; lh2 = lh2->l_next) { + for (i = 0; i < cpos; ++i) { + if (buf[i] != lh2->l_name[i]) + break; + } + /* if we have a match */ + if (i == cpos) { + /* if it wraps */ + if ((width + maxwidth) > ncol) { + addline(bp, linebuf); + linebuf[0] = '\0'; + width = 0; + } + len = strlcat(linebuf, lh2->l_name + preflen, + linesize); + width += maxwidth; + if (len < width && width < linesize) { + /* pad so the objects nicely line up */ + memset(linebuf + len, ' ', + maxwidth - strlen(lh2->l_name + preflen)); + linebuf[width] = '\0'; + } + } + } + if (width > 0) + addline(bp, linebuf); + free(linebuf); + + /* + * Note that we free lists only if they are put in wholelist lists + * that were built just for us should be freed. However when we use + * the buffer list, obviously we don't want it freed. + */ + free_file_list(wholelist); + popbuftop(bp, WEPHEM); /* split the screen and put up the help + * buffer */ + update(); /* needed to make the new stuff actually + * appear */ + ttmove(oldrow, oldcol); /* update leaves cursor in arbitrary place */ + ttcolor(oldhue); /* with arbitrary color */ + ttflush(); + return (0); +} + +/* + * The "lp1" and "lp2" point to list structures. The "cpos" is a horizontal + * position in the name. Return the longest block of characters that can be + * autocompleted at this point. Sometimes the two symbols are the same, but + * this is normal. + */ +int +getxtra(struct list *lp1, struct list *lp2, int cpos, int wflag) +{ + int i; + + i = cpos; + for (;;) { + if (lp1->l_name[i] != lp2->l_name[i]) + break; + if (lp1->l_name[i] == '\0') + break; + ++i; + if (wflag && !ISWORD(lp1->l_name[i - 1])) + break; + } + return (i - cpos); +} + +/* + * Special "printf" for the echo line. Each call to "ewprintf" starts a + * new line in the echo area, and ends with an erase to end of the echo + * line. The formatting is done by a call to the standard formatting + * routine. + */ +/* VARARGS */ +void +ewprintf(const char *fmt, ...) +{ + va_list ap; + + if (inmacro) + return; + + va_start(ap, fmt); + ttcolor(CTEXT); + ttmove(nrow - 1, 0); + eformat(fmt, ap); + va_end(ap); + tteeol(); + ttflush(); + epresf = TRUE; +} + +/* + * Printf style formatting. This is called by both "ewprintf" and "ereply" + * to provide formatting services to their clients. The move to the start + * of the echo line, and the erase to the end of the echo line, is done by + * the caller. + * %c prints the "name" of the supplied character. + * %k prints the name of the current key (and takes no arguments). + * %d prints a decimal integer + * %o prints an octal integer + * %p prints a pointer + * %s prints a string + * %ld prints a long word + * Anything else is echoed verbatim + */ +static void +eformat(const char *fp, va_list ap) +{ + char kname[NKNAME], tmp[100], *cp; + int c; + + while ((c = *fp++) != '\0') { + if (c != '%') + eputc(c); + else { + c = *fp++; + switch (c) { + case 'c': + getkeyname(kname, sizeof(kname), + va_arg(ap, int)); + eputs(kname); + break; + + case 'k': + for (cp = kname, c = 0; c < key.k_count; c++) { + if (c) + *cp++ = ' '; + cp = getkeyname(cp, sizeof(kname) - + (cp - kname) - 1, key.k_chars[c]); + } + eputs(kname); + break; + + case 'd': + eputi(va_arg(ap, int), 10); + break; + + case 'o': + eputi(va_arg(ap, int), 8); + break; + + case 'p': + snprintf(tmp, sizeof(tmp), "%p", + va_arg(ap, void *)); + eputs(tmp); + break; + + case 's': + eputs(va_arg(ap, char *)); + break; + + case 'l': + /* explicit longword */ + c = *fp++; + switch (c) { + case 'd': + eputl(va_arg(ap, long), 10); + break; + default: + eputc(c); + break; + } + break; + + default: + eputc(c); + } + } + } +} + +/* + * Put integer, in radix "r". + */ +static void +eputi(int i, int r) +{ + int q; + + if (i < 0) { + eputc('-'); + i = -i; + } + if ((q = i / r) != 0) + eputi(q, r); + eputc(i % r + '0'); +} + +/* + * Put long, in radix "r". + */ +static void +eputl(long l, int r) +{ + long q; + + if (l < 0) { + eputc('-'); + l = -l; + } + if ((q = l / r) != 0) + eputl(q, r); + eputc((int)(l % r) + '0'); +} + +/* + * Put string. + */ +static void +eputs(const char *s) +{ + int c; + + while ((c = *s++) != '\0') + eputc(c); +} + +/* + * Put character. Watch for control characters, and for the line getting + * too long. + */ +static void +eputc(char c) +{ + if (ttcol + 2 < ncol) { + if (ISCTRL(c)) { + eputc('^'); + c = CCHR(c); + } + ttputc(c); + ++ttcol; + } +} + +void +free_file_list(struct list *lp) +{ + struct list *next; + + while (lp) { + next = lp->l_next; + free(lp->l_name); + free(lp); + lp = next; + } +} + +static struct list * +copy_list(struct list *lp) +{ + struct list *current, *last, *nxt; + + last = NULL; + while (lp) { + current = malloc(sizeof(struct list)); + if (current == NULL) { + /* Free what we have allocated so far */ + for (current = last; current; current = nxt) { + nxt = current->l_next; + free(current->l_name); + free(current); + } + return (NULL); + } + current->l_next = last; + current->l_name = strdup(lp->l_name); + last = current; + lp = lp->l_next; + } + return (last); +} diff --git a/mg/extend.c b/mg/extend.c new file mode 100644 index 0000000..2f47de6 --- /dev/null +++ b/mg/extend.c @@ -0,0 +1,963 @@ +/* $OpenBSD: extend.c,v 1.53 2012/05/25 04:56:58 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Extended (M-X) commands, rebinding, and startup file processing. + */ +#include "chrdef.h" +#include "def.h" +#include "kbd.h" +#include "funmap.h" + +#include +#include + +#include "macro.h" + +#ifdef FKEYS +#include "key.h" +#ifndef BINDKEY +#define BINDKEY /* bindkey is used by FKEYS startup code */ +#endif /* !BINDKEY */ +#endif /* FKEYS */ + +#include + +static int remap(KEYMAP *, int, PF, KEYMAP *); +static KEYMAP *reallocmap(KEYMAP *); +static void fixmap(KEYMAP *, KEYMAP *, KEYMAP *); +static int dobind(KEYMAP *, const char *, int); +static char *skipwhite(char *); +static char *parsetoken(char *); +#ifdef BINDKEY +static int bindkey(KEYMAP **, const char *, KCHAR *, int); +#endif /* BINDKEY */ + +/* + * Insert a string, mainly for use from macros (created by selfinsert). + */ +/* ARGSUSED */ +int +insert(int f, int n) +{ + char buf[128], *bufp, *cp; + int count, c; + + if (inmacro) { + while (--n >= 0) { + for (count = 0; count < maclcur->l_used; count++) { + if ((((c = maclcur->l_text[count]) == '\n') + ? lnewline() : linsert(1, c)) != TRUE) + return (FALSE); + } + } + maclcur = maclcur->l_fp; + return (TRUE); + } + if (n == 1) + /* CFINS means selfinsert can tack on the end */ + thisflag |= CFINS; + + if ((bufp = eread("Insert: ", buf, sizeof(buf), EFNEW)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + while (--n >= 0) { + cp = buf; + while (*cp) { + if (((*cp == '\n') ? lnewline() : linsert(1, *cp)) + != TRUE) + return (FALSE); + cp++; + } + } + return (TRUE); +} + +/* + * Bind a key to a function. Cases range from the trivial (replacing an + * existing binding) to the extremely complex (creating a new prefix in a + * map_element that already has one, so the map_element must be split, + * but the keymap doesn't have enough room for another map_element, so + * the keymap is reallocated). No attempt is made to reclaim space no + * longer used, if this is a problem flags must be added to indicate + * malloced versus static storage in both keymaps and map_elements. + * Structure assignments would come in real handy, but K&R based compilers + * don't have them. Care is taken so running out of memory will leave + * the keymap in a usable state. + * Parameters are: + * curmap: pointer to the map being changed + * c: character being changed + * funct: function being changed to + * pref_map: if funct==NULL, map to bind to or NULL for new + */ +static int +remap(KEYMAP *curmap, int c, PF funct, KEYMAP *pref_map) +{ + int i, n1, n2, nold; + KEYMAP *mp, *newmap; + PF *pfp; + struct map_element *mep; + + if (ele >= &curmap->map_element[curmap->map_num] || c < ele->k_base) { + if (ele > &curmap->map_element[0] && (funct != NULL || + (ele - 1)->k_prefmap == NULL)) + n1 = c - (ele - 1)->k_num; + else + n1 = HUGE; + if (ele < &curmap->map_element[curmap->map_num] && + (funct != NULL || ele->k_prefmap == NULL)) + n2 = ele->k_base - c; + else + n2 = HUGE; + if (n1 <= MAPELEDEF && n1 <= n2) { + ele--; + if ((pfp = calloc(c - ele->k_base + 1, + sizeof(PF))) == NULL) { + ewprintf("Out of memory"); + return (FALSE); + } + nold = ele->k_num - ele->k_base + 1; + for (i = 0; i < nold; i++) + pfp[i] = ele->k_funcp[i]; + while (--n1) + pfp[i++] = curmap->map_default; + pfp[i] = funct; + ele->k_num = c; + ele->k_funcp = pfp; + } else if (n2 <= MAPELEDEF) { + if ((pfp = calloc(ele->k_num - c + 1, + sizeof(PF))) == NULL) { + ewprintf("Out of memory"); + return (FALSE); + } + nold = ele->k_num - ele->k_base + 1; + for (i = 0; i < nold; i++) + pfp[i + n2] = ele->k_funcp[i]; + while (--n2) + pfp[n2] = curmap->map_default; + pfp[0] = funct; + ele->k_base = c; + ele->k_funcp = pfp; + } else { + if (curmap->map_num >= curmap->map_max) { + if ((newmap = reallocmap(curmap)) == NULL) + return (FALSE); + curmap = newmap; + } + if ((pfp = malloc(sizeof(PF))) == NULL) { + ewprintf("Out of memory"); + return (FALSE); + } + pfp[0] = funct; + for (mep = &curmap->map_element[curmap->map_num]; + mep > ele; mep--) { + mep->k_base = (mep - 1)->k_base; + mep->k_num = (mep - 1)->k_num; + mep->k_funcp = (mep - 1)->k_funcp; + mep->k_prefmap = (mep - 1)->k_prefmap; + } + ele->k_base = c; + ele->k_num = c; + ele->k_funcp = pfp; + ele->k_prefmap = NULL; + curmap->map_num++; + } + if (funct == NULL) { + if (pref_map != NULL) + ele->k_prefmap = pref_map; + else { + if ((mp = malloc(sizeof(KEYMAP) + + (MAPINIT - 1) * sizeof(struct map_element))) == NULL) { + ewprintf("Out of memory"); + ele->k_funcp[c - ele->k_base] = + curmap->map_default; + return (FALSE); + } + mp->map_num = 0; + mp->map_max = MAPINIT; + mp->map_default = rescan; + ele->k_prefmap = mp; + } + } + } else { + n1 = c - ele->k_base; + if (ele->k_funcp[n1] == funct && (funct != NULL || + pref_map == NULL || pref_map == ele->k_prefmap)) + /* no change */ + return (TRUE); + if (funct != NULL || ele->k_prefmap == NULL) { + if (ele->k_funcp[n1] == NULL) + ele->k_prefmap = NULL; + /* easy case */ + ele->k_funcp[n1] = funct; + if (funct == NULL) { + if (pref_map != NULL) + ele->k_prefmap = pref_map; + else { + if ((mp = malloc(sizeof(KEYMAP) + + (MAPINIT - 1) * + sizeof(struct map_element))) == NULL) { + ewprintf("Out of memory"); + ele->k_funcp[c - ele->k_base] = + curmap->map_default; + return (FALSE); + } + mp->map_num = 0; + mp->map_max = MAPINIT; + mp->map_default = rescan; + ele->k_prefmap = mp; + } + } + } else { + /* + * This case is the splits. + * Determine which side of the break c goes on + * 0 = after break; 1 = before break + */ + n2 = 1; + for (i = 0; n2 && i < n1; i++) + n2 &= ele->k_funcp[i] != NULL; + if (curmap->map_num >= curmap->map_max) { + if ((newmap = reallocmap(curmap)) == NULL) + return (FALSE); + curmap = newmap; + } + if ((pfp = calloc(ele->k_num - c + !n2, + sizeof(PF))) == NULL) { + ewprintf("Out of memory"); + return (FALSE); + } + ele->k_funcp[n1] = NULL; + for (i = n1 + n2; i <= ele->k_num - ele->k_base; i++) + pfp[i - n1 - n2] = ele->k_funcp[i]; + for (mep = &curmap->map_element[curmap->map_num]; + mep > ele; mep--) { + mep->k_base = (mep - 1)->k_base; + mep->k_num = (mep - 1)->k_num; + mep->k_funcp = (mep - 1)->k_funcp; + mep->k_prefmap = (mep - 1)->k_prefmap; + } + ele->k_num = c - !n2; + (ele + 1)->k_base = c + n2; + (ele + 1)->k_funcp = pfp; + ele += !n2; + ele->k_prefmap = NULL; + curmap->map_num++; + if (pref_map == NULL) { + if ((mp = malloc(sizeof(KEYMAP) + (MAPINIT - 1) + * sizeof(struct map_element))) == NULL) { + ewprintf("Out of memory"); + ele->k_funcp[c - ele->k_base] = + curmap->map_default; + return (FALSE); + } + mp->map_num = 0; + mp->map_max = MAPINIT; + mp->map_default = rescan; + ele->k_prefmap = mp; + } else + ele->k_prefmap = pref_map; + } + } + return (TRUE); +} + +/* + * Reallocate a keymap. Returns NULL (without trashing the current map) + * on failure. + */ +static KEYMAP * +reallocmap(KEYMAP *curmap) +{ + struct maps_s *mps; + KEYMAP *mp; + int i; + + if (curmap->map_max > SHRT_MAX - MAPGROW) { + ewprintf("keymap too large"); + return (NULL); + } + if ((mp = malloc(sizeof(KEYMAP) + (curmap->map_max + (MAPGROW - 1)) * + sizeof(struct map_element))) == NULL) { + ewprintf("Out of memory"); + return (NULL); + } + mp->map_num = curmap->map_num; + mp->map_max = curmap->map_max + MAPGROW; + mp->map_default = curmap->map_default; + for (i = curmap->map_num; i--;) { + mp->map_element[i].k_base = curmap->map_element[i].k_base; + mp->map_element[i].k_num = curmap->map_element[i].k_num; + mp->map_element[i].k_funcp = curmap->map_element[i].k_funcp; + mp->map_element[i].k_prefmap = curmap->map_element[i].k_prefmap; + } + for (mps = maps; mps != NULL; mps = mps->p_next) { + if (mps->p_map == curmap) + mps->p_map = mp; + else + fixmap(curmap, mp, mps->p_map); + } + ele = &mp->map_element[ele - &curmap->map_element[0]]; + return (mp); +} + +/* + * Fix references to a reallocated keymap (recursive). + */ +static void +fixmap(KEYMAP *curmap, KEYMAP *mp, KEYMAP *mt) +{ + int i; + + for (i = mt->map_num; i--;) { + if (mt->map_element[i].k_prefmap != NULL) { + if (mt->map_element[i].k_prefmap == curmap) + mt->map_element[i].k_prefmap = mp; + else + fixmap(curmap, mp, mt->map_element[i].k_prefmap); + } + } +} + +/* + * Do the input for local-set-key, global-set-key and define-key + * then call remap to do the work. + */ +static int +dobind(KEYMAP *curmap, const char *p, int unbind) +{ + KEYMAP *pref_map = NULL; + PF funct; + char bprompt[80], *bufp, *pep; + int c, s, n; + + if (macrodef) { + /* + * Keystrokes aren't collected. Not hard, but pretty useless. + * Would not work for function keys in any case. + */ + ewprintf("Can't rebind key in macro"); + return (FALSE); + } + if (inmacro) { + for (s = 0; s < maclcur->l_used - 1; s++) { + if (doscan(curmap, c = CHARMASK(maclcur->l_text[s]), &curmap) + != NULL) { + if (remap(curmap, c, NULL, NULL) + != TRUE) + return (FALSE); + } + } + (void)doscan(curmap, c = maclcur->l_text[s], NULL); + maclcur = maclcur->l_fp; + } else { + n = strlcpy(bprompt, p, sizeof(bprompt)); + if (n >= sizeof(bprompt)) + n = sizeof(bprompt) - 1; + pep = bprompt + n; + for (;;) { + ewprintf("%s", bprompt); + pep[-1] = ' '; + pep = getkeyname(pep, sizeof(bprompt) - + (pep - bprompt), c = getkey(FALSE)); + if (doscan(curmap, c, &curmap) != NULL) + break; + *pep++ = '-'; + *pep = '\0'; + } + } + if (unbind) + funct = rescan; + else { + if ((bufp = eread("%s to command: ", bprompt, sizeof(bprompt), + EFFUNC | EFNEW, bprompt)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + if (((funct = name_function(bprompt)) == NULL) ? + (pref_map = name_map(bprompt)) == NULL : funct == NULL) { + ewprintf("[No match]"); + return (FALSE); + } + } + return (remap(curmap, c, funct, pref_map)); +} + +/* + * bindkey: bind key sequence to a function in the specified map. Used by + * excline so it can bind function keys. To close to release to change + * calling sequence, should just pass KEYMAP *curmap rather than + * KEYMAP **mapp. + */ +#ifdef BINDKEY +static int +bindkey(KEYMAP **mapp, const char *fname, KCHAR *keys, int kcount) +{ + KEYMAP *curmap = *mapp; + KEYMAP *pref_map = NULL; + PF funct; + int c; + + if (fname == NULL) + funct = rescan; + else if (((funct = name_function(fname)) == NULL) ? + (pref_map = name_map(fname)) == NULL : funct == NULL) { + ewprintf("[No match: %s]", fname); + return (FALSE); + } + while (--kcount) { + if (doscan(curmap, c = *keys++, &curmap) != NULL) { + if (remap(curmap, c, NULL, NULL) != TRUE) + return (FALSE); + /* + * XXX - Bizzarreness. remap creates an empty KEYMAP + * that the last key is supposed to point to. + */ + curmap = ele->k_prefmap; + } + } + (void)doscan(curmap, c = *keys, NULL); + return (remap(curmap, c, funct, pref_map)); +} + +#ifdef FKEYS +/* + * Wrapper for bindkey() that converts escapes. + */ +int +dobindkey(KEYMAP *map, const char *func, const char *str) +{ + int i; + + for (i = 0; *str && i < MAXKEY; i++) { + /* XXX - convert numbers w/ strol()? */ + if (*str == '^' && *(str + 1) != '\0') { + key.k_chars[i] = CCHR(toupper(*++str)); + } else if (*str == '\\' && *(str + 1) != '\0') { + switch (*++str) { + case '^': + key.k_chars[i] = '^'; + break; + case 't': + case 'T': + key.k_chars[i] = '\t'; + break; + case 'n': + case 'N': + key.k_chars[i] = '\n'; + break; + case 'r': + case 'R': + key.k_chars[i] = '\r'; + break; + case 'e': + case 'E': + key.k_chars[i] = CCHR('['); + break; + case '\\': + key.k_chars[i] = '\\'; + break; + } + } else + key.k_chars[i] = *str; + str++; + } + key.k_count = i; + return (bindkey(&map, func, key.k_chars, key.k_count)); +} +#endif /* FKEYS */ +#endif /* BINDKEY */ + +/* + * This function modifies the fundamental keyboard map. + */ +/* ARGSUSED */ +int +bindtokey(int f, int n) +{ + return (dobind(fundamental_map, "Global set key: ", FALSE)); +} + +/* + * This function modifies the current mode's keyboard map. + */ +/* ARGSUSED */ +int +localbind(int f, int n) +{ + return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map, + "Local set key: ", FALSE)); +} + +/* + * This function redefines a key in any keymap. + */ +/* ARGSUSED */ +int +redefine_key(int f, int n) +{ + static char buf[48]; + char tmp[32], *bufp; + KEYMAP *mp; + + (void)strlcpy(buf, "Define key map: ", sizeof(buf)); + if ((bufp = eread(buf, tmp, sizeof(tmp), EFNEW)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + (void)strlcat(buf, tmp, sizeof(buf)); + if ((mp = name_map(tmp)) == NULL) { + ewprintf("Unknown map %s", tmp); + return (FALSE); + } + if (strlcat(buf, "key: ", sizeof(buf)) >= sizeof(buf)) + return (FALSE); + + return (dobind(mp, buf, FALSE)); +} + +/* ARGSUSED */ +int +unbindtokey(int f, int n) +{ + return (dobind(fundamental_map, "Global unset key: ", TRUE)); +} + +/* ARGSUSED */ +int +localunbind(int f, int n) +{ + return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map, + "Local unset key: ", TRUE)); +} + +/* + * Extended command. Call the message line routine to read in the command + * name and apply autocompletion to it. When it comes back, look the name + * up in the symbol table and run the command if it is found. Print an + * error if there is anything wrong. + */ +int +extend(int f, int n) +{ + PF funct; + char xname[NXNAME], *bufp; + + if (!(f & FFARG)) + bufp = eread("M-x ", xname, NXNAME, EFNEW | EFFUNC); + else + bufp = eread("%d M-x ", xname, NXNAME, EFNEW | EFFUNC, n); + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + if ((funct = name_function(bufp)) != NULL) { + if (macrodef) { + struct line *lp = maclcur; + macro[macrocount - 1].m_funct = funct; + maclcur = lp->l_bp; + maclcur->l_fp = lp->l_fp; + free(lp); + } + return ((*funct)(f, n)); + } + ewprintf("[No match]"); + return (FALSE); +} + +/* + * Define the commands needed to do startup-file processing. + * This code is mostly a kludge just so we can get startup-file processing. + * + * If you're serious about having this code, you should rewrite it. + * To wit: + * It has lots of funny things in it to make the startup-file look + * like a GNU startup file; mostly dealing with parens and semicolons. + * This should all vanish. + * + * We define eval-expression because it's easy. It can make + * *-set-key or define-key set an arbitrary key sequence, so it isn't + * useless. + */ + +/* + * evalexpr - get one line from the user, and run it. + */ +/* ARGSUSED */ +int +evalexpr(int f, int n) +{ + char exbuf[128], *bufp; + + if ((bufp = eread("Eval: ", exbuf, sizeof(exbuf), + EFNEW | EFCR)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + return (excline(exbuf)); +} + +/* + * evalbuffer - evaluate the current buffer as line commands. Useful for + * testing startup files. + */ +/* ARGSUSED */ +int +evalbuffer(int f, int n) +{ + struct line *lp; + struct buffer *bp = curbp; + int s; + static char excbuf[128]; + + for (lp = bfirstlp(bp); lp != bp->b_headp; lp = lforw(lp)) { + if (llength(lp) >= 128) + return (FALSE); + (void)strncpy(excbuf, ltext(lp), llength(lp)); + + /* make sure it's terminated */ + excbuf[llength(lp)] = '\0'; + if ((s = excline(excbuf)) != TRUE) + return (s); + } + return (TRUE); +} + +/* + * evalfile - go get a file and evaluate it as line commands. You can + * go get your own startup file if need be. + */ +/* ARGSUSED */ +int +evalfile(int f, int n) +{ + char fname[NFILEN], *bufp; + + if ((bufp = eread("Load file: ", fname, NFILEN, + EFNEW | EFCR)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + return (load(fname)); +} + +/* + * load - go load the file name we got passed. + */ +int +load(const char *fname) +{ + int s = TRUE, line; + int nbytes = 0; + char excbuf[128]; + FILE *ffp; + + if ((fname = adjustname(fname, TRUE)) == NULL) + /* just to be careful */ + return (FALSE); + + if (ffropen(&ffp, fname, NULL) != FIOSUC) + return (FALSE); + + line = 0; + while ((s = ffgetline(ffp, excbuf, sizeof(excbuf) - 1, &nbytes)) + == FIOSUC) { + line++; + excbuf[nbytes] = '\0'; + if (excline(excbuf) != TRUE) { + s = FIOERR; + ewprintf("Error loading file %s at line %d", fname, line); + break; + } + } + (void)ffclose(ffp, NULL); + excbuf[nbytes] = '\0'; + if (s != FIOEOF || (nbytes && excline(excbuf) != TRUE)) + return (FALSE); + return (TRUE); +} + +/* + * excline - run a line from a load file or eval-expression. If FKEYS is + * defined, duplicate functionality of dobind so function key values don't + * have to fit in type char. + */ +int +excline(char *line) +{ + PF fp; + struct line *lp, *np; + int status, c, f, n; + char *funcp, *tmp; + char *argp = NULL; + long nl; +#ifdef FKEYS + int bind; + KEYMAP *curmap; +#define BINDARG 0 /* this arg is key to bind (local/global set key) */ +#define BINDNO 1 /* not binding or non-quoted BINDARG */ +#define BINDNEXT 2 /* next arg " (define-key) */ +#define BINDDO 3 /* already found key to bind */ +#define BINDEXT 1 /* space for trailing \0 */ +#else /* FKEYS */ +#define BINDEXT 0 +#endif /* FKEYS */ + + lp = NULL; + + if (macrodef || inmacro) { + ewprintf("Not now!"); + return (FALSE); + } + f = 0; + n = 1; + funcp = skipwhite(line); + if (*funcp == '\0') + return (TRUE); /* No error on blank lines */ + line = parsetoken(funcp); + if (*line != '\0') { + *line++ = '\0'; + line = skipwhite(line); + if (ISDIGIT(*line) || *line == '-') { + argp = line; + line = parsetoken(line); + } + } + if (argp != NULL) { + f = FFARG; + nl = strtol(argp, &tmp, 10); + if (*tmp != '\0') + return (FALSE); + if (nl >= INT_MAX || nl <= INT_MIN) + return (FALSE); + n = (int)nl; + } + if ((fp = name_function(funcp)) == NULL) { + ewprintf("Unknown function: %s", funcp); + return (FALSE); + } +#ifdef FKEYS + if (fp == bindtokey || fp == unbindtokey) { + bind = BINDARG; + curmap = fundamental_map; + } else if (fp == localbind || fp == localunbind) { + bind = BINDARG; + curmap = curbp->b_modes[curbp->b_nmodes]->p_map; + } else if (fp == redefine_key) + bind = BINDNEXT; + else + bind = BINDNO; +#endif /* FKEYS */ + /* Pack away all the args now... */ + if ((np = lalloc(0)) == FALSE) + return (FALSE); + np->l_fp = np->l_bp = maclcur = np; + while (*line != '\0') { + argp = skipwhite(line); + if (*argp == '\0') + break; + line = parsetoken(argp); + if (*argp != '"') { + if (*argp == '\'') + ++argp; + if ((lp = lalloc((int) (line - argp) + BINDEXT)) == + NULL) { + status = FALSE; + goto cleanup; + } + bcopy(argp, ltext(lp), (int)(line - argp)); +#ifdef FKEYS + /* don't count BINDEXT */ + lp->l_used--; + if (bind == BINDARG) + bind = BINDNO; +#endif /* FKEYS */ + } else { + /* quoted strings are special */ + ++argp; +#ifdef FKEYS + if (bind != BINDARG) { +#endif /* FKEYS */ + lp = lalloc((int)(line - argp) + BINDEXT); + if (lp == NULL) { + status = FALSE; + goto cleanup; + } + lp->l_used = 0; +#ifdef FKEYS + } else + key.k_count = 0; +#endif /* FKEYS */ + while (*argp != '"' && *argp != '\0') { + if (*argp != '\\') + c = *argp++; + else { + switch (*++argp) { + case 't': + case 'T': + c = CCHR('I'); + break; + case 'n': + case 'N': + c = CCHR('J'); + break; + case 'r': + case 'R': + c = CCHR('M'); + break; + case 'e': + case 'E': + c = CCHR('['); + break; + case '^': + /* + * split into two statements + * due to bug in OSK cpp + */ + c = CHARMASK(*++argp); + c = ISLOWER(c) ? + CCHR(TOUPPER(c)) : CCHR(c); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + c = *argp - '0'; + if (argp[1] <= '7' && + argp[1] >= '0') { + c <<= 3; + c += *++argp - '0'; + if (argp[1] <= '7' && + argp[1] >= '0') { + c <<= 3; + c += *++argp + - '0'; + } + } + break; +#ifdef FKEYS + case 'f': + case 'F': + c = *++argp - '0'; + if (ISDIGIT(argp[1])) { + c *= 10; + c += *++argp - '0'; + } + c += KFIRST; + break; +#endif /* FKEYS */ + default: + c = CHARMASK(*argp); + break; + } + argp++; + } +#ifdef FKEYS + if (bind == BINDARG) + key.k_chars[key.k_count++] = c; + else +#endif /* FKEYS */ + lp->l_text[lp->l_used++] = c; + } + if (*line) + line++; + } +#ifdef FKEYS + switch (bind) { + case BINDARG: + bind = BINDDO; + break; + case BINDNEXT: + lp->l_text[lp->l_used] = '\0'; + if ((curmap = name_map(lp->l_text)) == NULL) { + ewprintf("No such mode: %s", lp->l_text); + status = FALSE; + free(lp); + goto cleanup; + } + free(lp); + bind = BINDARG; + break; + default: +#endif /* FKEYS */ + lp->l_fp = np->l_fp; + lp->l_bp = np; + np->l_fp = lp; + np = lp; +#ifdef FKEYS + } +#endif /* FKEYS */ + } +#ifdef FKEYS + switch (bind) { + default: + ewprintf("Bad args to set key"); + status = FALSE; + break; + case BINDDO: + if (fp != unbindtokey && fp != localunbind) { + lp->l_text[lp->l_used] = '\0'; + status = bindkey(&curmap, lp->l_text, key.k_chars, + key.k_count); + } else + status = bindkey(&curmap, NULL, key.k_chars, + key.k_count); + break; + case BINDNO: +#endif /* FKEYS */ + inmacro = TRUE; + maclcur = maclcur->l_fp; + status = (*fp)(f, n); + inmacro = FALSE; +#ifdef FKEYS + } +#endif /* FKEYS */ +cleanup: + lp = maclcur->l_fp; + while (lp != maclcur) { + np = lp->l_fp; + free(lp); + lp = np; + } + free(lp); + return (status); +} + +/* + * a pair of utility functions for the above + */ +static char * +skipwhite(char *s) +{ + while (*s == ' ' || *s == '\t' || *s == ')' || *s == '(') + s++; + if (*s == ';') + *s = '\0'; + return (s); +} + +static char * +parsetoken(char *s) +{ + if (*s != '"') { + while (*s && *s != ' ' && *s != '\t' && *s != ')' && *s != '(') + s++; + if (*s == ';') + *s = '\0'; + } else + do { + /* + * Strings get special treatment. + * Beware: You can \ out the end of the string! + */ + if (*s == '\\') + ++s; + } while (*++s != '"' && *s != '\0'); + return (s); +} diff --git a/mg/file.c b/mg/file.c new file mode 100644 index 0000000..acf64c1 --- /dev/null +++ b/mg/file.c @@ -0,0 +1,715 @@ +/* $OpenBSD: file.c,v 1.81 2012/06/18 09:19:21 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * File commands. + */ + +#include "def.h" + +#include + +#include + +size_t xdirname(char *, const char *, size_t); + +/* + * Insert a file into the current buffer. Real easy - just call the + * insertfile routine with the file name. + */ +/* ARGSUSED */ +int +fileinsert(int f, int n) +{ + char fname[NFILEN], *bufp, *adjf; + + if (getbufcwd(fname, sizeof(fname)) != TRUE) + fname[0] = '\0'; + bufp = eread("Insert file: ", fname, NFILEN, + EFNEW | EFCR | EFFILE | EFDEF); + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + adjf = adjustname(bufp, TRUE); + if (adjf == NULL) + return (FALSE); + return (insertfile(adjf, NULL, FALSE)); +} + +/* + * Select a file for editing. Look around to see if you can find the file + * in another buffer; if you can find it, just switch to the buffer. If + * you cannot find the file, create a new buffer, read in the text, and + * switch to the new buffer. + */ +/* ARGSUSED */ +int +filevisit(int f, int n) +{ + struct buffer *bp; + char fname[NFILEN], *bufp, *adjf; + int status; + + if (getbufcwd(fname, sizeof(fname)) != TRUE) + fname[0] = '\0'; + bufp = eread("Find file: ", fname, NFILEN, + EFNEW | EFCR | EFFILE | EFDEF); + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + adjf = adjustname(fname, TRUE); + if (adjf == NULL) + return (FALSE); + if ((bp = findbuffer(adjf)) == NULL) + return (FALSE); + curbp = bp; + if (showbuffer(bp, curwp, WFFULL) != TRUE) + return (FALSE); + if (bp->b_fname[0] == '\0') { + if ((status = readin(adjf)) != TRUE) + killbuffer(bp); + return (status); + } + return (TRUE); +} + +/* + * Replace the current file with an alternate one. Semantics for finding + * the replacement file are the same as 'filevisit', except the current + * buffer is killed before the switch. If the kill fails, or is aborted, + * revert to the original file. + */ +/* ARGSUSED */ +int +filevisitalt(int f, int n) +{ + struct buffer *bp; + char fname[NFILEN], *bufp, *adjf; + int status; + + if (getbufcwd(fname, sizeof(fname)) != TRUE) + fname[0] = '\0'; + bufp = eread("Find alternate file: ", fname, NFILEN, + EFNEW | EFCR | EFFILE | EFDEF); + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + + status = killbuffer(curbp); + if (status == ABORT || status == FALSE) + return (ABORT); + + adjf = adjustname(fname, TRUE); + if (adjf == NULL) + return (FALSE); + if ((bp = findbuffer(adjf)) == NULL) + return (FALSE); + curbp = bp; + if (showbuffer(bp, curwp, WFFULL) != TRUE) + return (FALSE); + if (bp->b_fname[0] == '\0') { + if ((status = readin(adjf)) != TRUE) + killbuffer(bp); + return (status); + } + return (TRUE); +} + +int +filevisitro(int f, int n) +{ + int error; + + error = filevisit(f, n); + if (error != TRUE) + return (error); + curbp->b_flag |= BFREADONLY; + return (TRUE); +} + +/* + * Pop to a file in the other window. Same as the last function, but uses + * popbuf instead of showbuffer. + */ +/* ARGSUSED */ +int +poptofile(int f, int n) +{ + struct buffer *bp; + struct mgwin *wp; + char fname[NFILEN], *adjf, *bufp; + int status; + + if (getbufcwd(fname, sizeof(fname)) != TRUE) + fname[0] = '\0'; + if ((bufp = eread("Find file in other window: ", fname, NFILEN, + EFNEW | EFCR | EFFILE | EFDEF)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + adjf = adjustname(fname, TRUE); + if (adjf == NULL) + return (FALSE); + if ((bp = findbuffer(adjf)) == NULL) + return (FALSE); + if (bp == curbp) + return (splitwind(f, n)); + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + curbp = bp; + curwp = wp; + if (bp->b_fname[0] == '\0') { + if ((status = readin(adjf)) != TRUE) + killbuffer(bp); + return (status); + } + return (TRUE); +} + +/* + * Given a file name, either find the buffer it uses, or create a new + * empty buffer to put it in. + */ +struct buffer * +findbuffer(char *fn) +{ + struct buffer *bp; + char bname[NBUFN], fname[NBUFN]; + + if (strlcpy(fname, fn, sizeof(fname)) >= sizeof(fname)) { + ewprintf("filename too long"); + return (NULL); + } + + for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { + if (strcmp(bp->b_fname, fname) == 0) + return (bp); + } + /* Not found. Create a new one, adjusting name first */ + if (augbname(bname, fname, sizeof(bname)) == FALSE) + return (NULL); + + bp = bfind(bname, TRUE); + return (bp); +} + +/* + * Read the file "fname" into the current buffer. Make all of the text + * in the buffer go away, after checking for unsaved changes. This is + * called by the "read" command, the "visit" command, and the mainline + * (for "mg file"). + */ +int +readin(char *fname) +{ + struct mgwin *wp; + int status, i, ro = FALSE; + PF *ael; + + /* might be old */ + if (bclear(curbp) != TRUE) + return (TRUE); + /* Clear readonly. May be set by autoexec path */ + curbp->b_flag &= ~BFREADONLY; + if ((status = insertfile(fname, fname, TRUE)) != TRUE) { + ewprintf("File is not readable: %s", fname); + return (FALSE); + } + + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_bufp == curbp) { + wp->w_dotp = wp->w_linep = bfirstlp(curbp); + wp->w_doto = 0; + wp->w_markp = NULL; + wp->w_marko = 0; + } + } + + /* + * Call auto-executing function if we need to. + */ + if ((ael = find_autoexec(fname)) != NULL) { + for (i = 0; ael[i] != NULL; i++) + (*ael[i])(0, 1); + free(ael); + } + + /* no change */ + curbp->b_flag &= ~BFCHG; + + /* + * We need to set the READONLY flag after we insert the file, + * unless the file is a directory. + */ + if (access(fname, W_OK) && errno != ENOENT) + ro = TRUE; + if (fisdir(fname) == TRUE) + ro = TRUE; + if (ro == TRUE) + curbp->b_flag |= BFREADONLY; + + if (startrow) { + gotoline(FFARG, startrow); + startrow = 0; + } + + undo_add_modified(); + return (status); +} + +/* + * NB, getting file attributes is done here under control of a flag + * rather than in readin, which would be cleaner. I was concerned + * that some operating system might require the file to be open + * in order to get the information. Similarly for writing. + */ + +/* + * Insert a file in the current buffer, after dot. If file is a directory, + * and 'replacebuf' is TRUE, invoke dired mode, else die with an error. + * If file is a regular file, set mark at the end of the text inserted; + * point at the beginning. Return a standard status. Print a summary + * (lines read, error message) out as well. This routine also does the + * read end of backup processing. The BFBAK flag, if set in a buffer, + * says that a backup should be taken. It is set when a file is read in, + * but not on a new file. You don't need to make a backup copy of nothing. + */ + +static char *line = NULL; +static int linesize = 0; + +int +insertfile(char *fname, char *newname, int replacebuf) +{ + struct buffer *bp; + struct line *lp1, *lp2; + struct line *olp; /* line we started at */ + struct mgwin *wp; + int nbytes, s, nline = 0, siz, x, x2; + int opos; /* offset we started at */ + int oline; /* original line number */ + FILE *ffp; + + if (replacebuf == TRUE) + x = undo_enable(FFRAND, 0); + else + x = undo_enabled(); + + lp1 = NULL; + if (line == NULL) { + line = malloc(NLINE); + if (line == NULL) + panic("out of memory"); + linesize = NLINE; + } + + /* cheap */ + bp = curbp; + if (newname != NULL) { + (void)strlcpy(bp->b_fname, newname, sizeof(bp->b_fname)); + (void)xdirname(bp->b_cwd, newname, sizeof(bp->b_cwd)); + (void)strlcat(bp->b_cwd, "/", sizeof(bp->b_cwd)); + } + + /* hard file open */ + if ((s = ffropen(&ffp, fname, (replacebuf == TRUE) ? bp : NULL)) + == FIOERR) + goto out; + if (s == FIOFNF) { + /* file not found */ + if (newname != NULL) + ewprintf("(New file)"); + else + ewprintf("(File not found)"); + goto out; + } else if (s == FIODIR) { + /* file was a directory */ + if (replacebuf == FALSE) { + ewprintf("Cannot insert: file is a directory, %s", + fname); + goto cleanup; + } + killbuffer(bp); + bp = dired_(fname); + undo_enable(FFRAND, x); + if (bp == NULL) + return (FALSE); + curbp = bp; + return (showbuffer(bp, curwp, WFFULL | WFMODE)); + } else { + (void)xdirname(bp->b_cwd, fname, sizeof(bp->b_cwd)); + (void)strlcat(bp->b_cwd, "/", sizeof(bp->b_cwd)); + } + opos = curwp->w_doto; + oline = curwp->w_dotline; + /* + * Open a new line at dot and start inserting after it. + * We will delete this newline after insertion. + * Disable undo, as we create the undo record manually. + */ + x2 = undo_enable(FFRAND, 0); + (void)lnewline(); + olp = lback(curwp->w_dotp); + undo_enable(FFRAND, x2); + + nline = 0; + siz = 0; + while ((s = ffgetline(ffp, line, linesize, &nbytes)) != FIOERR) { +retry: + siz += nbytes + 1; + switch (s) { + case FIOSUC: + /* FALLTHRU */ + case FIOEOF: + ++nline; + if ((lp1 = lalloc(nbytes)) == NULL) { + /* keep message on the display */ + s = FIOERR; + undo_add_insert(olp, opos, + siz - nbytes - 1 - 1); + goto endoffile; + } + bcopy(line, <ext(lp1)[0], nbytes); + lp2 = lback(curwp->w_dotp); + lp2->l_fp = lp1; + lp1->l_fp = curwp->w_dotp; + lp1->l_bp = lp2; + curwp->w_dotp->l_bp = lp1; + if (s == FIOEOF) { + undo_add_insert(olp, opos, siz - 1); + goto endoffile; + } + break; + case FIOLONG: { + /* a line too long to fit in our buffer */ + char *cp; + int newsize; + + newsize = linesize * 2; + if (newsize < 0 || + (cp = malloc(newsize)) == NULL) { + ewprintf("Could not allocate %d bytes", + newsize); + s = FIOERR; + goto endoffile; + } + bcopy(line, cp, linesize); + free(line); + line = cp; + s = ffgetline(ffp, line + linesize, linesize, + &nbytes); + nbytes += linesize; + linesize = newsize; + if (s == FIOERR) + goto endoffile; + goto retry; + } + default: + ewprintf("Unknown code %d reading file", s); + s = FIOERR; + break; + } + } +endoffile: + /* ignore errors */ + (void)ffclose(ffp, NULL); + /* don't zap an error */ + if (s == FIOEOF) { + if (nline == 1) + ewprintf("(Read 1 line)"); + else + ewprintf("(Read %d lines)", nline); + } + /* set mark at the end of the text */ + curwp->w_dotp = curwp->w_markp = lback(curwp->w_dotp); + curwp->w_marko = llength(curwp->w_markp); + curwp->w_markline = oline + nline + 1; + /* + * if we are at the end of the file, ldelnewline is a no-op, + * but we still need to decrement the line and markline counts + * as we've accounted for this fencepost in our arithmetic + */ + if (lforw(curwp->w_dotp) == curwp->w_bufp->b_headp) { + curwp->w_bufp->b_lines--; + curwp->w_markline--; + } else + (void)ldelnewline(); + curwp->w_dotp = olp; + curwp->w_doto = opos; + curwp->w_dotline = oline; + if (olp == curbp->b_headp) + curwp->w_dotp = lforw(olp); + if (newname != NULL) + bp->b_flag |= BFCHG | BFBAK; /* Need a backup. */ + else + bp->b_flag |= BFCHG; + /* + * If the insert was at the end of buffer, set lp1 to the end of + * buffer line, and lp2 to the beginning of the newly inserted text. + * (Otherwise lp2 is set to NULL.) This is used below to set + * pointers in other windows correctly if they are also at the end of + * buffer. + */ + lp1 = bp->b_headp; + if (curwp->w_markp == lp1) { + lp2 = curwp->w_dotp; + } else { + /* delete extraneous newline */ + (void)ldelnewline(); +out: lp2 = NULL; + } + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_bufp == curbp) { + wp->w_rflag |= WFMODE | WFEDIT; + if (wp != curwp && lp2 != NULL) { + if (wp->w_dotp == lp1) + wp->w_dotp = lp2; + if (wp->w_markp == lp1) + wp->w_markp = lp2; + if (wp->w_linep == lp1) + wp->w_linep = lp2; + } + } + } + bp->b_lines += nline; +cleanup: + undo_enable(FFRAND, x); + + /* return FALSE if error */ + return (s != FIOERR); +} + +/* + * Ask for a file name and write the contents of the current buffer to that + * file. Update the remembered file name and clear the buffer changed flag. + * This handling of file names is different from the earlier versions and + * is more compatible with Gosling EMACS than with ITS EMACS. + */ +/* ARGSUSED */ +int +filewrite(int f, int n) +{ + struct stat statbuf; + int s; + char fname[NFILEN], bn[NBUFN], tmp[NFILEN + 25]; + char *adjfname, *bufp; + FILE *ffp; + + if (getbufcwd(fname, sizeof(fname)) != TRUE) + fname[0] = '\0'; + if ((bufp = eread("Write file: ", fname, NFILEN, + EFDEF | EFNEW | EFCR | EFFILE)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + + adjfname = adjustname(fname, TRUE); + if (adjfname == NULL) + return (FALSE); + + /* Check if file exists; write checks done later */ + if (stat(adjfname, &statbuf) == 0) { + snprintf(tmp, sizeof(tmp), "File `%s' exists; overwrite", + adjfname); + if ((s = eyorn(tmp)) != TRUE) + return (s); + } + + /* old attributes are no longer current */ + bzero(&curbp->b_fi, sizeof(curbp->b_fi)); + if ((s = writeout(&ffp, curbp, adjfname)) == TRUE) { + (void)strlcpy(curbp->b_fname, adjfname, sizeof(curbp->b_fname)); + if (getbufcwd(curbp->b_cwd, sizeof(curbp->b_cwd)) != TRUE) + (void)strlcpy(curbp->b_cwd, "/", sizeof(curbp->b_cwd)); + if (augbname(bn, curbp->b_fname, sizeof(bn)) + == FALSE) + return (FALSE); + free(curbp->b_bname); + if ((curbp->b_bname = strdup(bn)) == NULL) + return (FALSE); + (void)fupdstat(curbp); + curbp->b_flag &= ~(BFBAK | BFCHG); + upmodes(curbp); + } + return (s); +} + +/* + * Save the contents of the current buffer back into its associated file. + */ +#ifndef MAKEBACKUP +#define MAKEBACKUP TRUE +#endif /* !MAKEBACKUP */ +static int makebackup = MAKEBACKUP; + +/* ARGSUSED */ +int +filesave(int f, int n) +{ + if (curbp->b_fname[0] == '\0') + return (filewrite(f, n)); + else + return (buffsave(curbp)); +} + +/* + * Save the contents of the buffer argument into its associated file. Do + * nothing if there have been no changes (is this a bug, or a feature?). + * Error if there is no remembered file name. If this is the first write + * since the read or visit, then a backup copy of the file is made. + * Allow user to select whether or not to make backup files by looking at + * the value of makebackup. + */ +int +buffsave(struct buffer *bp) +{ + int s; + FILE *ffp; + + /* return, no changes */ + if ((bp->b_flag & BFCHG) == 0) { + ewprintf("(No changes need to be saved)"); + return (TRUE); + } + + /* must have a name */ + if (bp->b_fname[0] == '\0') { + ewprintf("No file name"); + return (FALSE); + } + + /* Ensure file has not been modified elsewhere */ + /* We don't use the ignore flag here */ + if (fchecktime(bp) != TRUE) { + if ((s = eyesno("File has changed on disk since last save. " + "Save anyway")) != TRUE) + return (s); + } + + if (makebackup && (bp->b_flag & BFBAK)) { + s = fbackupfile(bp->b_fname); + /* hard error */ + if (s == ABORT) + return (FALSE); + /* softer error */ + if (s == FALSE && + (s = eyesno("Backup error, save anyway")) != TRUE) + return (s); + } + if ((s = writeout(&ffp, bp, bp->b_fname)) == TRUE) { + (void)fupdstat(bp); + bp->b_flag &= ~(BFCHG | BFBAK); + upmodes(bp); + } + return (s); +} + +/* + * Since we don't have variables (we probably should) this is a command + * processor for changing the value of the make backup flag. If no argument + * is given, sets makebackup to true, so backups are made. If an argument is + * given, no backup files are made when saving a new version of a file. + */ +/* ARGSUSED */ +int +makebkfile(int f, int n) +{ + if (f & FFARG) + makebackup = n > 0; + else + makebackup = !makebackup; + ewprintf("Backup files %sabled", makebackup ? "en" : "dis"); + return (TRUE); +} + +/* + * NB: bp is passed to both ffwopen and ffclose because some + * attribute information may need to be updated at open time + * and others after the close. This is OS-dependent. Note + * that the ff routines are assumed to be able to tell whether + * the attribute information has been set up in this buffer + * or not. + */ + +/* + * This function performs the details of file writing; writing the file + * in buffer bp to file fn. Uses the file management routines in the + * "fileio.c" package. Most of the grief is checking of some sort. + * You may want to call fupdstat() after using this function. + */ +int +writeout(FILE ** ffp, struct buffer *bp, char *fn) +{ + int s; + + /* open writes message */ + if ((s = ffwopen(ffp, fn, bp)) != FIOSUC) + return (FALSE); + s = ffputbuf(*ffp, bp); + if (s == FIOSUC) { + /* no write error */ + s = ffclose(*ffp, bp); + if (s == FIOSUC) + ewprintf("Wrote %s", fn); + } else { + /* print a message indicating write error */ + (void)ffclose(*ffp, bp); + ewprintf("Unable to write %s", fn); + } + return (s == FIOSUC); +} + +/* + * Tag all windows for bp (all windows if bp == NULL) as needing their + * mode line updated. + */ +void +upmodes(struct buffer *bp) +{ + struct mgwin *wp; + + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) + if (bp == NULL || curwp->w_bufp == bp) + wp->w_rflag |= WFMODE; +} + +/* + * dirname using strlcpy semantic. + * Like dirname() except an empty string is returned in + * place of "/". This means we can always add a trailing + * slash and be correct. + * Address portability issues by copying argument + * before using. Some implementations modify the input string. + */ +size_t +xdirname(char *dp, const char *path, size_t dplen) +{ + char ts[NFILEN]; + size_t len; + + (void)strlcpy(ts, path, NFILEN); + len = strlcpy(dp, dirname(ts), dplen); + if (dplen > 0 && dp[0] == '/' && dp[1] == '\0') { + dp[0] = '\0'; + len = 0; + } + return (len); +} + +/* + * basename using strlcpy/strlcat semantic. + * Address portability issue by copying argument + * before using: some implementations modify the input string. + */ +size_t +xbasename(char *bp, const char *path, size_t bplen) +{ + char ts[NFILEN]; + + (void)strlcpy(ts, path, NFILEN); + return (strlcpy(bp, basename(ts), bplen)); +} diff --git a/mg/fileio.c b/mg/fileio.c new file mode 100644 index 0000000..0b63be8 --- /dev/null +++ b/mg/fileio.c @@ -0,0 +1,754 @@ +/* $OpenBSD: fileio.c,v 1.94 2012/07/10 06:28:12 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * POSIX fileio.c + */ +#include "def.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kbd.h" +#include "pathnames.h" + +static char *bkuplocation(const char *); +static int bkupleavetmp(const char *); +char *expandtilde(const char *); + +static char *bkupdir; +static int leavetmp = 0; /* 1 = leave any '~' files in tmp dir */ + +/* + * Open a file for reading. + */ +int +ffropen(FILE ** ffp, const char *fn, struct buffer *bp) +{ + if ((*ffp = fopen(fn, "r")) == NULL) { + if (errno == ENOENT) + return (FIOFNF); + return (FIOERR); + } + + /* If 'fn' is a directory open it with dired. */ + if (fisdir(fn) == TRUE) + return (FIODIR); + + ffstat(*ffp, bp); + + return (FIOSUC); +} + +/* + * Update stat/dirty info + */ +void +ffstat(FILE *ffp, struct buffer *bp) +{ + struct stat sb; + + if (bp && fstat(fileno(ffp), &sb) == 0) { + /* set highorder bit to make sure this isn't all zero */ + bp->b_fi.fi_mode = sb.st_mode | 0x8000; + bp->b_fi.fi_uid = sb.st_uid; + bp->b_fi.fi_gid = sb.st_gid; + bp->b_fi.fi_mtime = sb.st_mtimespec; + /* Clear the ignore flag */ + bp->b_flag &= ~(BFIGNDIRTY | BFDIRTY); + } +} + +/* + * Update the status/dirty info. If there is an error, + * there's not a lot we can do. + */ +int +fupdstat(struct buffer *bp) +{ + FILE *ffp; + + if ((ffp = fopen(bp->b_fname, "r")) == NULL) { + if (errno == ENOENT) + return (FIOFNF); + return (FIOERR); + } + ffstat(ffp, bp); + (void)ffclose(ffp, bp); + return (FIOSUC); +} + +/* + * Open a file for writing. + */ +int +ffwopen(FILE ** ffp, const char *fn, struct buffer *bp) +{ + int fd; + mode_t fmode = DEFFILEMODE; + + if (bp && bp->b_fi.fi_mode) + fmode = bp->b_fi.fi_mode & 07777; + + fd = open(fn, O_RDWR | O_CREAT | O_TRUNC, fmode); + if (fd == -1) { + ffp = NULL; + ewprintf("Cannot open file for writing : %s", strerror(errno)); + return (FIOERR); + } + + if ((*ffp = fdopen(fd, "w")) == NULL) { + ewprintf("Cannot open file for writing : %s", strerror(errno)); + close(fd); + return (FIOERR); + } + + /* + * If we have file information, use it. We don't bother to check for + * errors, because there's no a lot we can do about it. Certainly + * trying to change ownership will fail if we aren't root. That's + * probably OK. If we don't have info, no need to get it, since any + * future writes will do the same thing. + */ + if (bp && bp->b_fi.fi_mode) { + fchmod(fd, bp->b_fi.fi_mode & 07777); + fchown(fd, bp->b_fi.fi_uid, bp->b_fi.fi_gid); + } + return (FIOSUC); +} + +/* + * Close a file. + */ +/* ARGSUSED */ +int +ffclose(FILE *ffp, struct buffer *bp) +{ + if (fclose(ffp) == 0) + return (FIOSUC); + return (FIOERR); +} + +/* + * Write a buffer to the already opened file. bp points to the + * buffer. Return the status. + */ +int +ffputbuf(FILE *ffp, struct buffer *bp) +{ + struct line *lp, *lpend; + + lpend = bp->b_headp; + for (lp = lforw(lpend); lp != lpend; lp = lforw(lp)) { + if (fwrite(ltext(lp), 1, llength(lp), ffp) != llength(lp)) { + ewprintf("Write I/O error"); + return (FIOERR); + } + if (lforw(lp) != lpend) /* no implied \n on last line */ + putc('\n', ffp); + } + /* + * XXX should be variable controlled (once we have variables) + */ + if (llength(lback(lpend)) != 0) { + if (eyorn("No newline at end of file, add one") == TRUE) { + lnewline_at(lback(lpend), llength(lback(lpend))); + putc('\n', ffp); + } + } + return (FIOSUC); +} + +/* + * Read a line from a file, and store the bytes + * in the supplied buffer. Stop on end of file or end of + * line. When FIOEOF is returned, there is a valid line + * of data without the normally implied \n. + * If the line length exceeds nbuf, FIOLONG is returned. + */ +int +ffgetline(FILE *ffp, char *buf, int nbuf, int *nbytes) +{ + int c, i; + + i = 0; + while ((c = getc(ffp)) != EOF && c != '\n') { + buf[i++] = c; + if (i >= nbuf) + return (FIOLONG); + } + if (c == EOF && ferror(ffp) != FALSE) { + ewprintf("File read error"); + return (FIOERR); + } + *nbytes = i; + return (c == EOF ? FIOEOF : FIOSUC); +} + +/* + * Make a backup copy of "fname". On Unix the backup has the same + * name as the original file, with a "~" on the end; this seems to + * be newest of the new-speak. The error handling is all in "file.c". + * We do a copy instead of a rename since otherwise another process + * with an open fd will get the backup, not the new file. This is + * a problem when using mg with things like crontab and vipw. + */ +int +fbackupfile(const char *fn) +{ + struct stat sb; + int from, to, serrno; + ssize_t nread; + char buf[BUFSIZ]; + char *nname, *tname, *bkpth; + + if (stat(fn, &sb) == -1) { + ewprintf("Can't stat %s : %s", fn, strerror(errno)); + return (FALSE); + } + + if ((bkpth = bkuplocation(fn)) == NULL) + return (FALSE); + + if (asprintf(&nname, "%s~", bkpth) == -1) { + ewprintf("Can't allocate backup file name : %s", strerror(errno)); + free(bkpth); + return (ABORT); + } + if (asprintf(&tname, "%s.XXXXXXXXXX", bkpth) == -1) { + ewprintf("Can't allocate temp file name : %s", strerror(errno)); + free(bkpth); + free(nname); + return (ABORT); + } + free(bkpth); + + if ((from = open(fn, O_RDONLY)) == -1) { + free(nname); + free(tname); + return (FALSE); + } + to = mkstemp(tname); + if (to == -1) { + serrno = errno; + close(from); + free(nname); + free(tname); + errno = serrno; + return (FALSE); + } + while ((nread = read(from, buf, sizeof(buf))) > 0) { + if (write(to, buf, (size_t)nread) != nread) { + nread = -1; + break; + } + } + serrno = errno; + (void) fchmod(to, (sb.st_mode & 0777)); + close(from); + close(to); + if (nread == -1) { + if (unlink(tname) == -1) + ewprintf("Can't unlink temp : %s", strerror(errno)); + } else { + if (rename(tname, nname) == -1) { + ewprintf("Can't rename temp : %s", strerror(errno)); + (void) unlink(tname); + nread = -1; + } + } + free(nname); + free(tname); + errno = serrno; + + return (nread == -1 ? FALSE : TRUE); +} + +/* + * Convert "fn" to a canonicalized absolute filename, replacing + * a leading ~/ with the user's home dir, following symlinks, and + * and remove all occurrences of /./ and /../ + */ +char * +adjustname(const char *fn, int slashslash) +{ + static char fnb[MAXPATHLEN]; + const char *cp, *ep = NULL; + char *path; + + if (slashslash == TRUE) { + cp = fn + strlen(fn) - 1; + for (; cp >= fn; cp--) { + if (ep && (*cp == '/')) { + fn = ep; + break; + } + if (*cp == '/' || *cp == '~') + ep = cp; + else + ep = NULL; + } + } + if ((path = expandtilde(fn)) == NULL) + return (NULL); + + if (realpath(path, fnb) == NULL) + (void)strlcpy(fnb, path, sizeof(fnb)); + + free(path); + return (fnb); +} + +/* + * Find a startup file for the user and return its name. As a service + * to other pieces of code that may want to find a startup file (like + * the terminal driver in particular), accepts a suffix to be appended + * to the startup file name. + */ +char * +startupfile(char *suffix) +{ + static char file[NFILEN]; + char *home; + int ret; + + if ((home = getenv("HOME")) == NULL || *home == '\0') + goto nohome; + + if (suffix == NULL) { + ret = snprintf(file, sizeof(file), _PATH_MG_STARTUP, home); + if (ret < 0 || ret >= sizeof(file)) + return (NULL); + } else { + ret = snprintf(file, sizeof(file), _PATH_MG_TERM, home, suffix); + if (ret < 0 || ret >= sizeof(file)) + return (NULL); + } + + if (access(file, R_OK) == 0) + return (file); +nohome: +#ifdef STARTUPFILE + if (suffix == NULL) { + ret = snprintf(file, sizeof(file), "%s", STARTUPFILE); + if (ret < 0 || ret >= sizeof(file)) + return (NULL); + } else { + ret = snprintf(file, sizeof(file), "%s%s", STARTUPFILE, + suffix); + if (ret < 0 || ret >= sizeof(file)) + return (NULL); + } + + if (access(file, R_OK) == 0) + return (file); +#endif /* STARTUPFILE */ + return (NULL); +} + +int +copy(char *frname, char *toname) +{ + int ifd, ofd; + char buf[BUFSIZ]; + mode_t fmode = DEFFILEMODE; /* XXX?? */ + struct stat orig; + ssize_t sr; + + if ((ifd = open(frname, O_RDONLY)) == -1) + return (FALSE); + if (fstat(ifd, &orig) == -1) { + ewprintf("fstat: %s", strerror(errno)); + close(ifd); + return (FALSE); + } + + if ((ofd = open(toname, O_WRONLY|O_CREAT|O_TRUNC, fmode)) == -1) { + close(ifd); + return (FALSE); + } + while ((sr = read(ifd, buf, sizeof(buf))) > 0) { + if (write(ofd, buf, (size_t)sr) != sr) { + ewprintf("write error : %s", strerror(errno)); + break; + } + } + if (fchmod(ofd, orig.st_mode) == -1) + ewprintf("Cannot set original mode : %s", strerror(errno)); + + if (sr == -1) { + ewprintf("Read error : %s", strerror(errno)); + close(ifd); + close(ofd); + return (FALSE); + } + /* + * It is "normal" for this to fail since we can't guarantee that + * we will be running as root. + */ + if (fchown(ofd, orig.st_uid, orig.st_gid) && errno != EPERM) + ewprintf("Cannot set owner : %s", strerror(errno)); + + (void) close(ifd); + (void) close(ofd); + + return (TRUE); +} + +/* + * return list of file names that match the name in buf. + */ +struct list * +make_file_list(char *buf) +{ + char *dir, *file, *cp; + size_t len, preflen; + int ret; + DIR *dirp; + struct dirent *dent; + struct list *last, *current; + char fl_name[NFILEN + 2]; + char prefixx[NFILEN + 1]; + + /* + * We need three different strings: + + * dir - the name of the directory containing what the user typed. + * Must be a real unix file name, e.g. no ~user, etc.. + * Must not end in /. + * prefix - the portion of what the user typed that is before the + * names we are going to find in the directory. Must have a + * trailing / if the user typed it. + * names from the directory - We open dir, and return prefix + * concatenated with names. + */ + + /* first we get a directory name we can look up */ + /* + * Names ending in . are potentially odd, because adjustname will + * treat foo/bar/.. as a foo/, whereas we are + * interested in names starting with .. + */ + len = strlen(buf); + if (len && buf[len - 1] == '.') { + buf[len - 1] = 'x'; + dir = adjustname(buf, TRUE); + buf[len - 1] = '.'; + } else + dir = adjustname(buf, TRUE); + if (dir == NULL) + return (NULL); + /* + * If the user typed a trailing / or the empty string + * he wants us to use his file spec as a directory name. + */ + if (len && buf[len - 1] != '/') { + file = strrchr(dir, '/'); + if (file) { + *file = '\0'; + if (*dir == '\0') + dir = "/"; + } else + return (NULL); + } + /* Now we get the prefix of the name the user typed. */ + if (strlcpy(prefixx, buf, sizeof(prefixx)) >= sizeof(prefixx)) + return (NULL); + cp = strrchr(prefixx, '/'); + if (cp == NULL) + prefixx[0] = '\0'; + else + cp[1] = '\0'; + + preflen = strlen(prefixx); + /* cp is the tail of buf that really needs to be compared. */ + cp = buf + preflen; + len = strlen(cp); + + /* + * Now make sure that file names will fit in the buffers allocated. + * SV files are fairly short. For BSD, something more general would + * be required. + */ + if (preflen > NFILEN - MAXNAMLEN) + return (NULL); + + /* loop over the specified directory, making up the list of files */ + + /* + * Note that it is worth our time to filter out names that don't + * match, even though our caller is going to do so again, and to + * avoid doing the stat if completion is being done, because stat'ing + * every file in the directory is relatively expensive. + */ + + dirp = opendir(dir); + if (dirp == NULL) + return (NULL); + last = NULL; + + while ((dent = readdir(dirp)) != NULL) { + int isdir; + if (strncmp(cp, dent->d_name, len) != 0) + continue; + isdir = 0; + if (dent->d_type == DT_DIR) { + isdir = 1; + } else if (dent->d_type == DT_LNK || + dent->d_type == DT_UNKNOWN) { + struct stat statbuf; + char statname[NFILEN + 2]; + + statbuf.st_mode = 0; + ret = snprintf(statname, sizeof(statname), "%s/%s", + dir, dent->d_name); + if (ret < 0 || ret > sizeof(statname) - 1) + continue; + if (stat(statname, &statbuf) < 0) + continue; + if (S_ISDIR(statbuf.st_mode)) + isdir = 1; + } + + if ((current = malloc(sizeof(struct list))) == NULL) { + free_file_list(last); + closedir(dirp); + return (NULL); + } + ret = snprintf(fl_name, sizeof(fl_name), + "%s%s%s", prefixx, dent->d_name, isdir ? "/" : ""); + if (ret < 0 || ret >= sizeof(fl_name)) { + free(current); + continue; + } + current->l_next = last; + current->l_name = strdup(fl_name); + last = current; + } + closedir(dirp); + + return (last); +} + +/* + * Test if a supplied filename refers to a directory + * Returns ABORT on error, TRUE if directory. FALSE otherwise + */ +int +fisdir(const char *fname) +{ + struct stat statbuf; + + if (stat(fname, &statbuf) != 0) + return (ABORT); + + if (S_ISDIR(statbuf.st_mode)) + return (TRUE); + + return (FALSE); +} + +/* + * Check the mtime of the supplied filename. + * Return TRUE if last mtime matches, FALSE if not, + * If the stat fails, return TRUE and try the save anyway + */ +int +fchecktime(struct buffer *bp) +{ + struct stat sb; + + if (stat(bp->b_fname, &sb) == -1) + return (TRUE); + + if (bp->b_fi.fi_mtime.tv_sec != sb.st_mtimespec.tv_sec || + bp->b_fi.fi_mtime.tv_nsec != sb.st_mtimespec.tv_nsec) + return (FALSE); + + return (TRUE); + +} + +/* + * Location of backup file. This function creates the correct path. + */ +static char * +bkuplocation(const char *fn) +{ + struct stat sb; + char *ret; + + if (bkupdir != NULL && (stat(bkupdir, &sb) == 0) && + S_ISDIR(sb.st_mode) && !bkupleavetmp(fn)) { + char fname[NFILEN]; + const char *c; + int i = 0, len; + + c = fn; + len = strlen(bkupdir); + + while (*c != '\0') { + /* Make sure we don't go over combined: + * strlen(bkupdir + '/' + fname + '\0') + */ + if (i >= NFILEN - len - 1) + return (NULL); + if (*c == '/') { + fname[i] = '!'; + } else if (*c == '!') { + if (i >= NFILEN - len - 2) + return (NULL); + fname[i++] = '!'; + fname[i] = '!'; + } else + fname[i] = *c; + i++; + c++; + } + fname[i] = '\0'; + if (asprintf(&ret, "%s/%s", bkupdir, fname) == -1) + return (NULL); + + } else if ((ret = strndup(fn, NFILEN)) == NULL) + return (NULL); + + return (ret); +} + +int +backuptohomedir(int f, int n) +{ + const char *c = _PATH_MG_DIR; + char *p; + + if (bkupdir == NULL) { + p = adjustname(c, TRUE); + bkupdir = strndup(p, NFILEN); + if (bkupdir == NULL) + return(FALSE); + + if (mkdir(bkupdir, 0700) == -1 && errno != EEXIST) { + free(bkupdir); + bkupdir = NULL; + } + } else { + free(bkupdir); + bkupdir = NULL; + } + + return (TRUE); +} + +/* + * For applications that use mg as the editor and have a desire to keep + * '~' files in the TMPDIR, toggle the location: /tmp | ~/.mg.d + */ +int +toggleleavetmp(int f, int n) +{ + leavetmp = !leavetmp; + + return (TRUE); +} + +/* + * Returns TRUE if fn is located in the temp directory and we want to save + * those backups there. + */ +int +bkupleavetmp(const char *fn) +{ + char *tmpdir, *tmp = NULL; + + if (!leavetmp) + return(FALSE); + + if((tmpdir = getenv("TMPDIR")) != NULL && *tmpdir != '\0') { + tmp = strstr(fn, tmpdir); + if (tmp == fn) + return (TRUE); + + return (FALSE); + } + + tmp = strstr(fn, "/tmp"); + if (tmp == fn) + return (TRUE); + + return (FALSE); +} + +/* + * Expand file names beginning with '~' if appropriate: + * 1, if ./~fn exists, continue without expanding tilde. + * 2, else, if username 'fn' exists, expand tilde with home directory path. + * 3, otherwise, continue and create new buffer called ~fn. + */ +char * +expandtilde(const char *fn) +{ + struct passwd *pw; + struct stat statbuf; + const char *cp; + char user[LOGIN_NAME_MAX], path[NFILEN]; + char *un, *ret; + size_t ulen, plen; + + path[0] = '\0'; + + if (fn[0] != '~' || stat(fn, &statbuf) == 0) { + if ((ret = strndup(fn, NFILEN)) == NULL) + return (NULL); + return(ret); + } + cp = strchr(fn, '/'); + if (cp == NULL) + cp = fn + strlen(fn); /* point to the NUL byte */ + ulen = cp - &fn[1]; + if (ulen >= sizeof(user)) { + if ((ret = strndup(fn, NFILEN)) == NULL) + return (NULL); + return(ret); + } + if (ulen == 0) { /* ~/ or ~ */ + if ((un = getlogin()) != NULL) + (void)strlcpy(user, un, sizeof(user)); + else + user[0] = '\0'; + } else { /* ~user/ or ~user */ + memcpy(user, &fn[1], ulen); + user[ulen] = '\0'; + } + pw = getpwnam(user); + if (pw != NULL) { + plen = strlcpy(path, pw->pw_dir, sizeof(path)); + if (plen == 0 || path[plen - 1] != '/') { + if (strlcat(path, "/", sizeof(path)) >= sizeof(path)) { + ewprintf("Path too long"); + return (NULL); + } + } + fn = cp; + if (*fn == '/') + fn++; + } + if (strlcat(path, fn, sizeof(path)) >= sizeof(path)) { + ewprintf("Path too long"); + return (NULL); + } + if ((ret = strndup(path, NFILEN)) == NULL) + return (NULL); + + return (ret); +} diff --git a/mg/funmap.c b/mg/funmap.c new file mode 100644 index 0000000..aac58ce --- /dev/null +++ b/mg/funmap.c @@ -0,0 +1,281 @@ +/* $OpenBSD: funmap.c,v 1.40 2012/06/14 17:21:22 lum Exp $ */ + +/* This file is in the public domain */ + +#include "def.h" +#include "kbd.h" +#include "funmap.h" + +/* + * If the function is NULL, it must be listed with the + * same name in the map_table. + */ + +struct funmap { + PF fn_funct; + const char *fn_name; + struct funmap *fn_next; +}; + +static struct funmap *funs; + +static struct funmap functnames[] = { + {apropos_command, "apropos",}, + {auto_execute, "auto-execute", }, + {fillmode, "auto-fill-mode",}, + {indentmode, "auto-indent-mode",}, + {backtoindent, "back-to-indentation",}, + {backuptohomedir, "backup-to-home-directory",}, + {backchar, "backward-char",}, + {delbword, "backward-kill-word",}, + {gotobop, "backward-paragraph",}, + {backword, "backward-word",}, + {gotobob, "beginning-of-buffer",}, + {gotobol, "beginning-of-line",}, + {showmatch, "blink-and-insert",}, + {bsmap, "bsmap-mode",}, + {NULL, "c-x 4 prefix",}, + {NULL, "c-x prefix",}, + {executemacro, "call-last-kbd-macro",}, + {capword, "capitalize-word",}, + {changedir, "cd",}, + {clearmark, "clear-mark",}, + {copyregion, "copy-region-as-kill",}, +#ifdef REGEX + {cntmatchlines, "count-matches",}, + {cntnonmatchlines, "count-non-matches",}, +#endif /* REGEX */ + {redefine_key, "define-key",}, + {backdel, "delete-backward-char",}, + {deblank, "delete-blank-lines",}, + {forwdel, "delete-char",}, + {delwhite, "delete-horizontal-space",}, + {delleadwhite, "delete-leading-space",}, + {deltrailwhite, "delete-trailing-space",}, +#ifdef REGEX + {delmatchlines, "delete-matching-lines",}, + {delnonmatchlines, "delete-non-matching-lines",}, +#endif /* REGEX */ + {onlywind, "delete-other-windows",}, + {delwind, "delete-window",}, + {wallchart, "describe-bindings",}, + {desckey, "describe-key-briefly",}, + {digit_argument, "digit-argument",}, + {lowerregion, "downcase-region",}, + {lowerword, "downcase-word",}, + {showversion, "emacs-version",}, + {finishmacro, "end-kbd-macro",}, + {globalwdtoggle, "global-wd-mode",}, + {gotoeob, "end-of-buffer",}, + {gotoeol, "end-of-line",}, + {enlargewind, "enlarge-window",}, + {NULL, "esc prefix",}, + {evalbuffer, "eval-current-buffer",}, + {evalexpr, "eval-expression",}, + {swapmark, "exchange-point-and-mark",}, + {extend, "execute-extended-command",}, + {fillpara, "fill-paragraph",}, + {filevisit, "find-file",}, + {filevisitro, "find-file-read-only",}, + {filevisitalt, "find-alternate-file",}, + {poptofile, "find-file-other-window",}, + {forwchar, "forward-char",}, + {gotoeop, "forward-paragraph",}, + {forwword, "forward-word",}, + {bindtokey, "global-set-key",}, + {unbindtokey, "global-unset-key",}, + {gotoline, "goto-line",}, + {help_help, "help-help",}, + {insert, "insert",}, + {bufferinsert, "insert-buffer",}, + {fileinsert, "insert-file",}, + {fillword, "insert-with-wrap",}, + {backisearch, "isearch-backward",}, + {forwisearch, "isearch-forward",}, + {joinline, "join-line",}, + {justone, "just-one-space",}, + {ctrlg, "keyboard-quit",}, + {killbuffer_cmd, "kill-buffer",}, + {killline, "kill-line",}, + {killpara, "kill-paragraph",}, + {killregion, "kill-region",}, + {delfword, "kill-word",}, + {toggleleavetmp, "leave-tmpdir-backups",}, + {linenotoggle, "line-number-mode",}, + {listbuffers, "list-buffers",}, + {evalfile, "load",}, + {localbind, "local-set-key",}, + {localunbind, "local-unset-key",}, + {makebkfile, "make-backup-files",}, + {markbuffer, "mark-whole-buffer",}, + {do_meta, "meta-key-mode",}, /* better name, anyone? */ + {negative_argument, "negative-argument",}, + {newline, "newline",}, + {lfindent, "newline-and-indent",}, + {indent, "indent-current-line",}, + {forwline, "next-line",}, +#ifdef NOTAB + {notabmode, "no-tab-mode",}, +#endif /* NOTAB */ + {notmodified, "not-modified",}, + {openline, "open-line",}, + {nextwind, "other-window",}, + {overwrite_mode, "overwrite-mode",}, + {prefixregion, "prefix-region",}, + {backline, "previous-line",}, + {prevwind, "previous-window",}, + {poptag, "pop-tag-mark",}, + {spawncli, "push-shell",}, + {findtag, "find-tag",}, + {tagsvisit, "visit-tags-table",}, + {showcwdir, "pwd",}, + {queryrepl, "query-replace",}, +#ifdef REGEX + {replstr, "replace-string",}, + {re_queryrepl, "query-replace-regexp",}, +#endif /* REGEX */ + {quote, "quoted-insert",}, +#ifdef REGEX + {re_searchagain, "re-search-again",}, + {re_backsearch, "re-search-backward",}, + {re_forwsearch, "re-search-forward",}, +#endif /* REGEX */ + {reposition, "recenter",}, + {redraw, "redraw-display",}, + {filesave, "save-buffer",}, + {quit, "save-buffers-kill-emacs",}, + {savebuffers, "save-some-buffers",}, + {backpage, "scroll-down",}, + {back1page, "scroll-one-line-down",}, + {forw1page, "scroll-one-line-up",}, + {pagenext, "scroll-other-window",}, + {forwpage, "scroll-up",}, + {searchagain, "search-again",}, + {backsearch, "search-backward",}, + {forwsearch, "search-forward",}, + {selfinsert, "self-insert-command",}, +#ifdef REGEX + {setcasefold, "set-case-fold-search",}, +#endif /* REGEX */ + {set_default_mode, "set-default-mode",}, + {setfillcol, "set-fill-column",}, + {setmark, "set-mark-command",}, + {setprefix, "set-prefix-string",}, + {piperegion, "shell-command-on-region",}, + {shrinkwind, "shrink-window",}, +#ifdef NOTAB + {space_to_tabstop, "space-to-tabstop",}, +#endif /* NOTAB */ + {splitwind, "split-window-vertically",}, + {definemacro, "start-kbd-macro",}, + {spawncli, "suspend-emacs",}, + {usebuffer, "switch-to-buffer",}, + {poptobuffer, "switch-to-buffer-other-window",}, + {togglereadonly, "toggle-read-only" }, + {twiddle, "transpose-chars",}, + {undo, "undo", }, + {undo_enable, "undo-enable", }, + {undo_boundary_enable, "undo-boundary-toggle", }, + {undo_add_boundary, "undo-boundary", }, + {undo_dump, "undo-list", }, + {universal_argument, "universal-argument",}, + {upperregion, "upcase-region",}, + {upperword, "upcase-word",}, + {showcpos, "what-cursor-position",}, + {filewrite, "write-file",}, + {yank, "yank",}, + {cssymbol, "cscope-find-this-symbol",}, + {csdefinition, "cscope-find-global-definition",}, + {csfuncalled, "cscope-find-called-functions",}, + {cscallerfuncs, "cscope-find-functions-calling-this-function",}, + {csfindtext, "cscope-find-this-text-string",}, + {csegrep, "cscope-find-egrep-pattern",}, + {csfindfile, "cscope-find-this-file",}, + {csfindinc, "cscope-find-files-including-file",}, + {csnextmatch, "cscope-next-symbol",}, + {csprevmatch, "cscope-prev-symbol",}, + {csnextfile, "cscope-next-file",}, + {csprevfile, "cscope-prev-file",}, + {cscreatelist, "cscope-create-list-of-files-to-index"}, + {NULL, NULL,} +}; + +void +funmap_init(void) +{ + struct funmap *fn; + + for (fn = functnames; fn->fn_name != NULL; fn++) { + fn->fn_next = funs; + funs = fn; + } +} + +int +funmap_add(PF fun, const char *fname) +{ + struct funmap *fn; + + if ((fn = malloc(sizeof(*fn))) == NULL) + return (FALSE); + + fn->fn_funct = fun; + fn->fn_name = fname; + fn->fn_next = funs; + + funs = fn; + return (TRUE); +} + +/* + * Translate from function name to function pointer. + */ +PF +name_function(const char *fname) +{ + struct funmap *fn; + + for (fn = funs; fn != NULL; fn = fn->fn_next) { + if (strcmp(fn->fn_name, fname) == 0) + return (fn->fn_funct); + } + return (NULL); +} + +const char * +function_name(PF fun) +{ + struct funmap *fn; + + for (fn = funs; fn != NULL; fn = fn->fn_next) { + if (fn->fn_funct == fun) + return (fn->fn_name); + } + return (NULL); +} + +/* + * List possible function name completions. + */ +struct list * +complete_function_list(const char *fname) +{ + struct funmap *fn; + struct list *head, *el; + int len; + + len = strlen(fname); + head = NULL; + for (fn = funs; fn != NULL; fn = fn->fn_next) { + if (memcmp(fname, fn->fn_name, len) == 0) { + if ((el = malloc(sizeof(*el))) == NULL) { + free_file_list(head); + return (NULL); + } + el->l_name = strdup(fn->fn_name); + el->l_next = head; + head = el; + } + } + return (head); +} diff --git a/mg/funmap.h b/mg/funmap.h new file mode 100644 index 0000000..acc24ae --- /dev/null +++ b/mg/funmap.h @@ -0,0 +1,9 @@ +/* $OpenBSD: funmap.h,v 1.7 2008/06/10 00:19:31 kjell Exp $ */ + +/* This file is in the public domain */ + +void funmap_init(void); +PF name_function(const char *); +const char *function_name(PF); +struct list *complete_function_list(const char *); +int funmap_add(PF, const char *); diff --git a/mg/grep.c b/mg/grep.c new file mode 100644 index 0000000..0d688b5 --- /dev/null +++ b/mg/grep.c @@ -0,0 +1,365 @@ +/* $OpenBSD: grep.c,v 1.38 2009/06/04 23:39:37 kjell Exp $ */ + +/* This file is in the public domain */ + +#include "def.h" +#include "kbd.h" +#include "funmap.h" + +#include +#include +#include +#include + +int globalwd = FALSE; +static int compile_goto_error(int, int); +int next_error(int, int); +static int grep(int, int); +static int gid(int, int); +static struct buffer *compile_mode(const char *, const char *); +static int xlint(int, int); +void grep_init(void); + +static char compile_last_command[NFILEN] = "make "; + +/* + * Hints for next-error + * + * XXX - need some kind of callback to find out when those get killed. + */ +struct mgwin *compile_win; +struct buffer *compile_buffer; + +static PF compile_pf[] = { + compile_goto_error +}; + +static struct KEYMAPE (1 + IMAPEXT) compilemap = { + 1, + 1 + IMAPEXT, + rescan, + { + { CCHR('M'), CCHR('M'), compile_pf, NULL } + } +}; + +void +grep_init(void) +{ + funmap_add(compile_goto_error, "compile-goto-error"); + funmap_add(next_error, "next-error"); + funmap_add(grep, "grep"); + funmap_add(xlint, "lint"); + funmap_add(compile, "compile"); + funmap_add(gid, "gid"); + maps_add((KEYMAP *)&compilemap, "compile"); +} + +/* ARGSUSED */ +static int +grep(int f, int n) +{ + char cprompt[NFILEN], *bufp; + struct buffer *bp; + struct mgwin *wp; + + (void)strlcpy(cprompt, "grep -n ", sizeof(cprompt)); + if ((bufp = eread("Run grep: ", cprompt, NFILEN, + EFDEF | EFNEW | EFCR)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + if (strlcat(cprompt, " /dev/null", sizeof(cprompt)) >= sizeof(cprompt)) + return (FALSE); + + if ((bp = compile_mode("*grep*", cprompt)) == NULL) + return (FALSE); + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + curbp = bp; + compile_win = curwp = wp; + return (TRUE); +} + +/* ARGSUSED */ +static int +xlint(int f, int n) +{ + char cprompt[NFILEN], *bufp; + struct buffer *bp; + struct mgwin *wp; + + (void)strlcpy(cprompt, "make lint ", sizeof(cprompt)); + if ((bufp = eread("Run lint: ", cprompt, NFILEN, + EFDEF | EFNEW | EFCR)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + + if ((bp = compile_mode("*lint*", cprompt)) == NULL) + return (FALSE); + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + curbp = bp; + compile_win = curwp = wp; + return (TRUE); +} + +/* ARGSUSED */ +int +compile(int f, int n) +{ + char cprompt[NFILEN], *bufp; + struct buffer *bp; + struct mgwin *wp; + + (void)strlcpy(cprompt, compile_last_command, sizeof(cprompt)); + if ((bufp = eread("Compile command: ", cprompt, NFILEN, + EFDEF | EFNEW | EFCR)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + if (savebuffers(f, n) == ABORT) + return (ABORT); + (void)strlcpy(compile_last_command, bufp, sizeof(compile_last_command)); + + if ((bp = compile_mode("*compile*", cprompt)) == NULL) + return (FALSE); + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + curbp = bp; + compile_win = curwp = wp; + gotoline(FFARG, 0); + return (TRUE); +} + +/* id-utils foo. */ +/* ARGSUSED */ +static int +gid(int f, int n) +{ + char command[NFILEN]; + char cprompt[NFILEN], c, *bufp; + struct buffer *bp; + struct mgwin *wp; + int i, j, len; + + /* catch ([^\s(){}]+)[\s(){}]* */ + + i = curwp->w_doto; + /* Skip backwards over delimiters we are currently on */ + while (i > 0) { + c = lgetc(curwp->w_dotp, i); + if (isalnum(c) || c == '_') + break; + + i--; + } + + /* Skip the symbol itself */ + for (; i > 0; i--) { + c = lgetc(curwp->w_dotp, i - 1); + if (!isalnum(c) && c != '_') + break; + } + /* Fill the symbol in cprompt[] */ + for (j = 0; j < sizeof(cprompt) - 1 && i < llength(curwp->w_dotp); + j++, i++) { + c = lgetc(curwp->w_dotp, i); + if (!isalnum(c) && c != '_') + break; + cprompt[j] = c; + } + cprompt[j] = '\0'; + + if ((bufp = eread("Run gid (with args): ", cprompt, NFILEN, + (j ? EFDEF : 0) | EFNEW | EFCR)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + len = snprintf(command, sizeof(command), "gid %s", cprompt); + if (len < 0 || len >= sizeof(command)) + return (FALSE); + + if ((bp = compile_mode("*gid*", command)) == NULL) + return (FALSE); + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + curbp = bp; + compile_win = curwp = wp; + return (TRUE); +} + +struct buffer * +compile_mode(const char *name, const char *command) +{ + struct buffer *bp; + FILE *fpipe; + char *buf; + size_t len; + int ret, n; + char cwd[NFILEN], qcmd[NFILEN]; + char timestr[NTIME]; + time_t t; + + n = snprintf(qcmd, sizeof(qcmd), "%s 2>&1", command); + if (n < 0 || n >= sizeof(qcmd)) + return (NULL); + + bp = bfind(name, TRUE); + if (bclear(bp) != TRUE) + return (NULL); + + if (getbufcwd(bp->b_cwd, sizeof(bp->b_cwd)) != TRUE) + return (NULL); + addlinef(bp, "cd %s", bp->b_cwd); + addline(bp, qcmd); + addline(bp, ""); + + if (getcwd(cwd, sizeof(cwd)) == NULL) + panic("Can't get current directory!"); + if (chdir(bp->b_cwd) == -1) { + ewprintf("Can't change dir to %s", bp->b_cwd); + return (NULL); + } + if ((fpipe = popen(qcmd, "r")) == NULL) { + ewprintf("Problem opening pipe"); + return (NULL); + } + /* + * We know that our commands are nice and the last line will end with + * a \n, so we don't need to try to deal with the last line problem + * in fgetln. + */ + while ((buf = fgetln(fpipe, &len)) != NULL) { + buf[len - 1] = '\0'; + addline(bp, buf); + } + ret = pclose(fpipe); + t = time(NULL); + strftime(timestr, sizeof(timestr), "%a %b %e %T %Y", localtime(&t)); + addline(bp, ""); + if (ret != 0) + addlinef(bp, "Command exited abnormally with code %d" + " at %s", ret, timestr); + else + addlinef(bp, "Command finished at %s", timestr); + + bp->b_dotp = bfirstlp(bp); + bp->b_modes[0] = name_mode("fundamental"); + bp->b_modes[1] = name_mode("compile"); + bp->b_nmodes = 1; + + compile_buffer = bp; + + if (chdir(cwd) == -1) { + ewprintf("Can't change dir back to %s", cwd); + return (NULL); + } + return (bp); +} + +/* ARGSUSED */ +static int +compile_goto_error(int f, int n) +{ + struct buffer *bp; + struct mgwin *wp; + char *fname, *line, *lp, *ln; + int lineno; + char *adjf, path[NFILEN]; + const char *errstr; + struct line *last; + + compile_win = curwp; + compile_buffer = curbp; + last = blastlp(compile_buffer); + + retry: + /* last line is compilation result */ + if (curwp->w_dotp == last) + return (FALSE); + + if ((line = linetostr(curwp->w_dotp)) == NULL) + return (FALSE); + lp = line; + if ((fname = strsep(&lp, ":")) == NULL || *fname == '\0') + goto fail; + if ((ln = strsep(&lp, ":")) == NULL || *ln == '\0') + goto fail; + lineno = (int)strtonum(ln, INT_MIN, INT_MAX, &errstr); + if (errstr) + goto fail; + + if (fname && fname[0] != '/') { + if (getbufcwd(path, sizeof(path)) == FALSE) + goto fail; + if (strlcat(path, fname, sizeof(path)) >= sizeof(path)) + goto fail; + adjf = path; + } else { + adjf = adjustname(fname, TRUE); + } + free(line); + + if (adjf == NULL) + return (FALSE); + + if ((bp = findbuffer(adjf)) == NULL) + return (FALSE); + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + curbp = bp; + curwp = wp; + if (bp->b_fname[0] == '\0') + readin(adjf); + gotoline(FFARG, lineno); + return (TRUE); +fail: + free(line); + if (curwp->w_dotp != blastlp(curbp)) { + curwp->w_dotp = lforw(curwp->w_dotp); + curwp->w_rflag |= WFMOVE; + goto retry; + } + ewprintf("No more hits"); + return (FALSE); +} + +/* ARGSUSED */ +int +next_error(int f, int n) +{ + if (compile_win == NULL || compile_buffer == NULL) { + ewprintf("No compilation active"); + return (FALSE); + } + curwp = compile_win; + curbp = compile_buffer; + if (curwp->w_dotp == blastlp(curbp)) { + ewprintf("No more hits"); + return (FALSE); + } + curwp->w_dotp = lforw(curwp->w_dotp); + curwp->w_rflag |= WFMOVE; + + return (compile_goto_error(f, n)); +} + +/* + * Since we don't have variables (we probably should) these are command + * processors for changing the values of mode flags. + */ +/* ARGSUSED */ +int +globalwdtoggle(int f, int n) +{ + if (f & FFARG) + globalwd = n > 0; + else + globalwd = !globalwd; + + sgarbf = TRUE; + + return (TRUE); +} diff --git a/mg/help.c b/mg/help.c new file mode 100644 index 0000000..938f813 --- /dev/null +++ b/mg/help.c @@ -0,0 +1,231 @@ +/* $OpenBSD: help.c,v 1.34 2012/04/12 04:47:59 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Help functions for Mg 2 + */ + +#include "def.h" +#include "funmap.h" + +#include "kbd.h" +#include "key.h" +#include "macro.h" + +static int showall(struct buffer *, KEYMAP *, char *); +static int findbind(KEYMAP *, PF, char *, size_t); + +/* + * Read a key from the keyboard, and look it up in the keymap. + * Display the name of the function currently bound to the key. + */ +/* ARGSUSED */ +int +desckey(int f, int n) +{ + KEYMAP *curmap; + PF funct; + int c, m, i, num; + char *pep; + char dprompt[80]; + + if (inmacro) + return (TRUE); /* ignore inside keyboard macro */ + + num = strlcpy(dprompt, "Describe key briefly: ", sizeof(dprompt)); + if (num >= sizeof(dprompt)) + num = sizeof(dprompt) - 1; + pep = dprompt + num; + key.k_count = 0; + m = curbp->b_nmodes; + curmap = curbp->b_modes[m]->p_map; + for (;;) { + for (;;) { + ewprintf("%s", dprompt); + pep[-1] = ' '; + pep = getkeyname(pep, sizeof(dprompt) - (pep - dprompt), + key.k_chars[key.k_count++] = c = getkey(FALSE)); + if ((funct = doscan(curmap, c, &curmap)) != NULL) + break; + *pep++ = '-'; + *pep = '\0'; + } + if (funct != rescan) + break; + if (ISUPPER(key.k_chars[key.k_count - 1])) { + funct = doscan(curmap, + TOLOWER(key.k_chars[key.k_count - 1]), &curmap); + if (funct == NULL) { + *pep++ = '-'; + *pep = '\0'; + continue; + } + if (funct != rescan) + break; + } +nextmode: + if (--m < 0) + break; + curmap = curbp->b_modes[m]->p_map; + for (i = 0; i < key.k_count; i++) { + funct = doscan(curmap, key.k_chars[i], &curmap); + if (funct != NULL) { + if (i == key.k_count - 1 && funct != rescan) + goto found; + funct = rescan; + goto nextmode; + } + } + *pep++ = '-'; + *pep = '\0'; + } +found: + if (funct == rescan || funct == selfinsert) + ewprintf("%k is not bound to any function"); + else if ((pep = (char *)function_name(funct)) != NULL) + ewprintf("%k runs the command %s", pep); + else + ewprintf("%k is bound to an unnamed function"); + return (TRUE); +} + +/* + * This function creates a table, listing all of the command + * keys and their current bindings, and stores the table in the + * *help* pop-up buffer. This lets Mg produce it's own wall chart. + */ +/* ARGSUSED */ +int +wallchart(int f, int n) +{ + int m; + struct buffer *bp; + + bp = bfind("*help*", TRUE); + if (bclear(bp) != TRUE) + /* clear it out */ + return (FALSE); + bp->b_flag |= BFREADONLY; + for (m = curbp->b_nmodes; m > 0; m--) { + if ((addlinef(bp, "Local keybindings for mode %s:", + curbp->b_modes[m]->p_name) == FALSE) || + (showall(bp, curbp->b_modes[m]->p_map, "") == FALSE) || + (addline(bp, "") == FALSE)) + return (FALSE); + } + if ((addline(bp, "Global bindings:") == FALSE) || + (showall(bp, fundamental_map, "") == FALSE)) + return (FALSE); + return (popbuftop(bp, WNONE)); +} + +static int +showall(struct buffer *bp, KEYMAP *map, char *prefix) +{ + KEYMAP *newmap; + char buf[80], keybuf[16]; + PF fun; + int c; + + if (addline(bp, "") == FALSE) + return (FALSE); + + /* XXX - 256 ? */ + for (c = 0; c < 256; c++) { + fun = doscan(map, c, &newmap); + if (fun == rescan || fun == selfinsert) + continue; + getkeyname(buf, sizeof(buf), c); + (void)snprintf(keybuf, sizeof(keybuf), "%s%s ", prefix, buf); + if (fun == NULL) { + if (showall(bp, newmap, keybuf) == FALSE) + return (FALSE); + } else { + if (addlinef(bp, "%-16s%s", keybuf, + function_name(fun)) == FALSE) + return (FALSE); + } + } + return (TRUE); +} + +int +help_help(int f, int n) +{ + KEYMAP *kp; + PF funct; + + if ((kp = name_map("help")) == NULL) + return (FALSE); + ewprintf("a b c: "); + do { + funct = doscan(kp, getkey(FALSE), NULL); + } while (funct == NULL || funct == help_help); + + if (macrodef && macrocount < MAXMACRO) + macro[macrocount - 1].m_funct = funct; + + return ((*funct)(f, n)); +} + +/* ARGSUSED */ +int +apropos_command(int f, int n) +{ + struct buffer *bp; + struct list *fnames, *el; + char string[32]; + + if (eread("apropos: ", string, sizeof(string), EFNUL | EFNEW) == NULL) + return (ABORT); + /* FALSE means we got a 0 character string, which is fine */ + bp = bfind("*help*", TRUE); + if (bclear(bp) == FALSE) + return (FALSE); + + fnames = complete_function_list(""); + for (el = fnames; el != NULL; el = el->l_next) { + char buf[32]; + + if (strstr(el->l_name, string) == NULL) + continue; + + buf[0] = '\0'; + findbind(fundamental_map, name_function(el->l_name), + buf, sizeof(buf)); + + if (addlinef(bp, "%-32s%s", el->l_name, buf) == FALSE) { + free_file_list(fnames); + return (FALSE); + } + } + free_file_list(fnames); + return (popbuftop(bp, WNONE)); +} + +static int +findbind(KEYMAP *map, PF fun, char *buf, size_t len) +{ + KEYMAP *newmap; + PF nfun; + char buf2[16], keybuf[16]; + int c; + + /* XXX - 256 ? */ + for (c = 0; c < 256; c++) { + nfun = doscan(map, c, &newmap); + if (nfun == fun) { + getkeyname(buf, len, c); + return (TRUE); + } + if (nfun == NULL) { + if (findbind(newmap, fun, buf2, sizeof(buf2)) == TRUE) { + getkeyname(keybuf, sizeof(keybuf), c); + (void)snprintf(buf, len, "%s %s", keybuf, buf2); + return (TRUE); + } + } + } + return (FALSE); +} diff --git a/mg/kbd.c b/mg/kbd.c new file mode 100644 index 0000000..0eb3c36 --- /dev/null +++ b/mg/kbd.c @@ -0,0 +1,436 @@ +/* $OpenBSD: kbd.c,v 1.25 2012/04/12 04:47:59 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Terminal independent keyboard handling. + */ + +#include "def.h" +#include "kbd.h" +#include "key.h" +#include "macro.h" + +#ifndef METABIT +#define METABIT 0x80 +#endif /* !METABIT */ + +#ifndef NO_DPROMPT +#define PROMPTL 80 +char prompt[PROMPTL] = "", *promptp = prompt; +#endif /* !NO_DPROMPT */ + +static int mgwrap(PF, int, int); + +static int use_metakey = TRUE; +static int pushed = FALSE; +static int pushedc; + +struct map_element *ele; + +struct key key; + +/* + * Toggle the value of use_metakey + */ +int +do_meta(int f, int n) +{ + if (f & FFARG) + use_metakey = n > 0; + else + use_metakey = !use_metakey; + ewprintf("Meta keys %sabled", use_metakey ? "en" : "dis"); + return (TRUE); +} + +static int bs_map = 0; + +/* + * Toggle backspace mapping + */ +int +bsmap(int f, int n) +{ + if (f & FFARG) + bs_map = n > 0; + else + bs_map = !bs_map; + ewprintf("Backspace mapping %sabled", bs_map ? "en" : "dis"); + return (TRUE); +} + +void +ungetkey(int c) +{ + if (use_metakey && pushed && c == CCHR('[')) + pushedc |= METABIT; + else + pushedc = c; + pushed = TRUE; +} + +int +getkey(int flag) +{ + int c; + +#ifndef NO_DPROMPT + if (flag && !pushed) { + if (prompt[0] != '\0' && ttwait(2000)) { + /* avoid problems with % */ + ewprintf("%s", prompt); + /* put the cursor back */ + update(); + epresf = KCLEAR; + } + if (promptp > prompt) + *(promptp - 1) = ' '; + } +#endif /* !NO_DPROMPT */ + if (pushed) { + c = pushedc; + pushed = FALSE; + } else + c = ttgetc(); + + if (bs_map) { + if (c == CCHR('H')) + c = CCHR('?'); + else if (c == CCHR('?')) + c = CCHR('H'); + } + if (use_metakey && (c & METABIT)) { + pushedc = c & ~METABIT; + pushed = TRUE; + c = CCHR('['); + } +#ifndef NO_DPROMPT + if (flag && promptp < &prompt[PROMPTL - 5]) { + promptp = getkeyname(promptp, + sizeof(prompt) - (promptp - prompt) - 1, c); + *promptp++ = '-'; + *promptp = '\0'; + } +#endif /* !NO_DPROMPT */ + return (c); +} + +/* + * doscan scans a keymap for a keyboard character and returns a pointer + * to the function associated with that character. Sets ele to the + * keymap element the keyboard was found in as a side effect. + */ +PF +doscan(KEYMAP *map, int c, KEYMAP **newmap) +{ + struct map_element *elec = &map->map_element[0]; + struct map_element *last = &map->map_element[map->map_num]; + PF ret; + + while (elec < last && c > elec->k_num) + elec++; + + /* used by prefix and binding code */ + ele = elec; + if (elec >= last || c < elec->k_base) + ret = map->map_default; + else + ret = elec->k_funcp[c - elec->k_base]; + if (ret == NULL && newmap != NULL) + *newmap = elec->k_prefmap; + + return (ret); +} + +int +doin(void) +{ + KEYMAP *curmap; + PF funct; + +#ifndef NO_DPROMPT + *(promptp = prompt) = '\0'; +#endif /* !NO_DPROMPT */ + curmap = curbp->b_modes[curbp->b_nmodes]->p_map; + key.k_count = 0; + while ((funct = doscan(curmap, (key.k_chars[key.k_count++] = + getkey(TRUE)), &curmap)) == NULL) + /* nothing */; + + if (macrodef && macrocount < MAXMACRO) + macro[macrocount++].m_funct = funct; + + return (mgwrap(funct, 0, 1)); +} + +int +rescan(int f, int n) +{ + int c; + KEYMAP *curmap; + int i; + PF fp = NULL; + int md = curbp->b_nmodes; + + for (;;) { + if (ISUPPER(key.k_chars[key.k_count - 1])) { + c = TOLOWER(key.k_chars[key.k_count - 1]); + curmap = curbp->b_modes[md]->p_map; + for (i = 0; i < key.k_count - 1; i++) { + if ((fp = doscan(curmap, (key.k_chars[i]), + &curmap)) != NULL) + break; + } + if (fp == NULL) { + if ((fp = doscan(curmap, c, NULL)) == NULL) + while ((fp = doscan(curmap, + key.k_chars[key.k_count++] = + getkey(TRUE), &curmap)) == NULL) + /* nothing */; + if (fp != rescan) { + if (macrodef && macrocount <= MAXMACRO) + macro[macrocount - 1].m_funct + = fp; + return (mgwrap(fp, f, n)); + } + } + } + /* try previous mode */ + if (--md < 0) + return (ABORT); + curmap = curbp->b_modes[md]->p_map; + for (i = 0; i < key.k_count; i++) { + if ((fp = doscan(curmap, (key.k_chars[i]), &curmap)) != NULL) + break; + } + if (fp == NULL) { + while ((fp = doscan(curmap, key.k_chars[i++] = + getkey(TRUE), &curmap)) == NULL) + /* nothing */; + key.k_count = i; + } + if (fp != rescan && i >= key.k_count - 1) { + if (macrodef && macrocount <= MAXMACRO) + macro[macrocount - 1].m_funct = fp; + return (mgwrap(fp, f, n)); + } + } +} + +int +universal_argument(int f, int n) +{ + KEYMAP *curmap; + PF funct; + int c, nn = 4; + + if (f & FFUNIV) + nn *= n; + for (;;) { + key.k_chars[0] = c = getkey(TRUE); + key.k_count = 1; + if (c == '-') + return (negative_argument(f, nn)); + if (c >= '0' && c <= '9') + return (digit_argument(f, nn)); + curmap = curbp->b_modes[curbp->b_nmodes]->p_map; + while ((funct = doscan(curmap, c, &curmap)) == NULL) { + key.k_chars[key.k_count++] = c = getkey(TRUE); + } + if (funct != universal_argument) { + if (macrodef && macrocount < MAXMACRO - 1) { + if (f & FFARG) + macrocount--; + macro[macrocount++].m_count = nn; + macro[macrocount++].m_funct = funct; + } + return (mgwrap(funct, FFUNIV, nn)); + } + nn <<= 2; + } +} + +/* ARGSUSED */ +int +digit_argument(int f, int n) +{ + KEYMAP *curmap; + PF funct; + int nn, c; + + nn = key.k_chars[key.k_count - 1] - '0'; + for (;;) { + c = getkey(TRUE); + if (c < '0' || c > '9') + break; + nn *= 10; + nn += c - '0'; + } + key.k_chars[0] = c; + key.k_count = 1; + curmap = curbp->b_modes[curbp->b_nmodes]->p_map; + while ((funct = doscan(curmap, c, &curmap)) == NULL) { + key.k_chars[key.k_count++] = c = getkey(TRUE); + } + if (macrodef && macrocount < MAXMACRO - 1) { + if (f & FFARG) + macrocount--; + else + macro[macrocount - 1].m_funct = universal_argument; + macro[macrocount++].m_count = nn; + macro[macrocount++].m_funct = funct; + } + return (mgwrap(funct, FFOTHARG, nn)); +} + +int +negative_argument(int f, int n) +{ + KEYMAP *curmap; + PF funct; + int c; + int nn = 0; + + for (;;) { + c = getkey(TRUE); + if (c < '0' || c > '9') + break; + nn *= 10; + nn += c - '0'; + } + if (nn) + nn = -nn; + else + nn = -n; + key.k_chars[0] = c; + key.k_count = 1; + curmap = curbp->b_modes[curbp->b_nmodes]->p_map; + while ((funct = doscan(curmap, c, &curmap)) == NULL) { + key.k_chars[key.k_count++] = c = getkey(TRUE); + } + if (macrodef && macrocount < MAXMACRO - 1) { + if (f & FFARG) + macrocount--; + else + macro[macrocount - 1].m_funct = universal_argument; + macro[macrocount++].m_count = nn; + macro[macrocount++].m_funct = funct; + } + return (mgwrap(funct, FFNEGARG, nn)); +} + +/* + * Insert a character. While defining a macro, create a "LINE" containing + * all inserted characters. + */ +int +selfinsert(int f, int n) +{ + struct line *lp; + int c; + int count; + + if (n < 0) + return (FALSE); + if (n == 0) + return (TRUE); + c = key.k_chars[key.k_count - 1]; + + if (macrodef && macrocount < MAXMACRO) { + if (f & FFARG) + macrocount -= 2; + + /* last command was insert -- tack on the end */ + if (lastflag & CFINS) { + macrocount--; + /* Ensure the line can handle the new characters */ + if (maclcur->l_size < maclcur->l_used + n) { + if (lrealloc(maclcur, maclcur->l_used + n) == + FALSE) + return (FALSE); + } + maclcur->l_used += n; + /* Copy in the new data */ + for (count = maclcur->l_used - n; + count < maclcur->l_used; count++) + maclcur->l_text[count] = c; + } else { + macro[macrocount - 1].m_funct = insert; + if ((lp = lalloc(n)) == NULL) + return (FALSE); + lp->l_bp = maclcur; + lp->l_fp = maclcur->l_fp; + maclcur->l_fp = lp; + maclcur = lp; + for (count = 0; count < n; count++) + lp->l_text[count] = c; + } + thisflag |= CFINS; + } + if (c == '\n') { + do { + count = lnewline(); + } while (--n && count == TRUE); + return (count); + } + + /* overwrite mode */ + if (curbp->b_flag & BFOVERWRITE) { + lchange(WFEDIT); + while (curwp->w_doto < llength(curwp->w_dotp) && n--) + lputc(curwp->w_dotp, curwp->w_doto++, c); + if (n <= 0) + return (TRUE); + } + return (linsert(n, c)); +} + +/* + * This could be implemented as a keymap with everything defined as self-insert. + */ +int +quote(int f, int n) +{ + int c; + + key.k_count = 1; + if ((key.k_chars[0] = getkey(TRUE)) >= '0' && key.k_chars[0] <= '7') { + key.k_chars[0] -= '0'; + if ((c = getkey(TRUE)) >= '0' && c <= '7') { + key.k_chars[0] <<= 3; + key.k_chars[0] += c - '0'; + if ((c = getkey(TRUE)) >= '0' && c <= '7') { + key.k_chars[0] <<= 3; + key.k_chars[0] += c - '0'; + } else + ungetkey(c); + } else + ungetkey(c); + } + return (selfinsert(f, n)); +} + +/* + * Wraper function to count invocation repeats. + * We ignore any function whose sole purpose is to get us + * to the intended function. + */ +static int +mgwrap(PF funct, int f, int n) +{ + static PF ofp; + + if (funct != rescan && + funct != negative_argument && + funct != digit_argument && + funct != universal_argument) { + if (funct == ofp) + rptcount++; + else + rptcount = 0; + ofp = funct; + } + + return ((*funct)(f, n)); +} diff --git a/mg/kbd.h b/mg/kbd.h new file mode 100644 index 0000000..67b244c --- /dev/null +++ b/mg/kbd.h @@ -0,0 +1,58 @@ +/* $OpenBSD: kbd.h,v 1.18 2006/07/27 19:59:29 deraadt Exp $ */ + +/* This file is in the public domain. */ + +/* + * kbd.h: type definitions for symbol.c and kbd.c for mg experimental + */ + +struct map_element { + KCHAR k_base; /* first key in element */ + KCHAR k_num; /* last key in element */ + PF *k_funcp; /* pointer to array of pointers */ + /* to functions */ + struct keymap_s *k_prefmap; /* keymap of ONLY prefix key in */ + /* element */ +}; + +/* + * Predefined keymaps are NOT type KEYMAP because final array needs + * dimension. If any changes are made to this struct, they must be reflected + * in all keymap declarations. + */ + +#define KEYMAPE(NUM) { \ + short map_num; /* elements used */ \ + short map_max; /* elements allocated */\ + PF map_default; /* default function */ \ + struct map_element map_element[NUM]; /* really [e_max] */ \ +} +typedef struct keymap_s KEYMAPE(1) KEYMAP; + +/* Number of map_elements to grow an overflowed keymap by */ +#define IMAPEXT 0 +#define MAPGROW 3 +#define MAPINIT (MAPGROW+1) + +/* Max number of default bindings added to avoid creating new element */ +#define MAPELEDEF 4 + +struct maps_s { + KEYMAP *p_map; + const char *p_name; + struct maps_s *p_next; +}; + +extern struct maps_s *maps; +extern struct maps_s fundamental_mode; +#define fundamental_map (fundamental_mode.p_map) + +int dobindkey(KEYMAP *, const char *, const char *); +KEYMAP *name_map(const char *); +struct maps_s *name_mode(const char *); +PF doscan(KEYMAP *, int, KEYMAP **); +void maps_init(void); +int maps_add(KEYMAP *, const char *); + +extern struct map_element *ele; +extern struct maps_s *defb_modes[]; diff --git a/mg/key.h b/mg/key.h new file mode 100644 index 0000000..3b2a7cf --- /dev/null +++ b/mg/key.h @@ -0,0 +1,14 @@ +/* $OpenBSD: key.h,v 1.5 2005/06/14 18:14:40 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* key.h: Insert file for mg 2 functions that need to reference key pressed */ + +#define MAXKEY 8 /* maximum number of prefix chars */ + +struct key { /* the chacter sequence in a key */ + int k_count; /* number of chars */ + KCHAR k_chars[MAXKEY]; /* chars */ +}; + +extern struct key key; diff --git a/mg/keymap.c b/mg/keymap.c new file mode 100644 index 0000000..94aead6 --- /dev/null +++ b/mg/keymap.c @@ -0,0 +1,567 @@ +/* $OpenBSD: keymap.c,v 1.50 2012/06/07 15:15:04 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Keyboard maps. This is character set dependent. The terminal specific + * parts of building the keymap has been moved to a better place. + */ + +#include "def.h" +#include "kbd.h" + +/* + * initial keymap declarations, deepest first + */ + +static PF cHcG[] = { + ctrlg, /* ^G */ + help_help /* ^H */ +}; + +static PF cHa[] = { + apropos_command, /* a */ + wallchart, /* b */ + desckey /* c */ +}; + +struct KEYMAPE (2 + IMAPEXT) helpmap = { + 2, + 2 + IMAPEXT, + rescan, + { + { + CCHR('G'), CCHR('H'), cHcG, NULL + }, + { + 'a', 'c', cHa, NULL + } + } +}; + +static PF cCsc[] = { + cscallerfuncs, /* c */ + csdefinition, /* d */ + csegrep, /* e */ + csfindfile, /* f */ + rescan, /* g */ + rescan, /* h */ + csfindinc, /* i */ + rescan, /* j */ + rescan, /* k */ + rescan, /* l */ + rescan, /* m */ + csnextmatch, /* n */ + rescan, /* o */ + csprevmatch, /* p */ + rescan, /* q */ + rescan, /* r */ + cssymbol, /* s */ + csfindtext /* t */ +}; + +static struct KEYMAPE (1 + IMAPEXT) cCsmap = { + 1, + 1 + IMAPEXT, + rescan, + { + { + 'c', 't', cCsc, NULL + } + } +}; + +static PF cCs[] = { + NULL /* s */ +}; + +struct KEYMAPE (2 + IMAPEXT) ccmap = { + 2, + 2 + IMAPEXT, + rescan, + { + { + CCHR('@'), CCHR('@'), (PF[]){ rescan }, NULL + }, + { + 's', 's', cCs, (KEYMAP *) & cCsmap + } + } +}; + +static PF cX4cF[] = { + poptofile, /* ^f */ + ctrlg /* ^g */ +}; +static PF cX4b[] = { + poptobuffer, /* b */ + rescan, /* c */ + rescan, /* d */ + rescan, /* e */ + poptofile /* f */ +}; +static struct KEYMAPE (2 + IMAPEXT) cX4map = { + 2, + 2 + IMAPEXT, + rescan, + { + { + CCHR('F'), CCHR('G'), cX4cF, NULL + }, + { + 'b', 'f', cX4b, NULL + } + } +}; + +static PF cXcB[] = { + listbuffers, /* ^B */ + quit, /* ^C */ + rescan, /* ^D */ + rescan, /* ^E */ + filevisit, /* ^F */ + ctrlg /* ^G */ +}; + +static PF cXcL[] = { + lowerregion, /* ^L */ + rescan, /* ^M */ + rescan, /* ^N */ + deblank, /* ^O */ + rescan, /* ^P */ + togglereadonly, /* ^Q */ + filevisitro, /* ^R */ + filesave, /* ^S */ + rescan, /* ^T */ + upperregion, /* ^U */ + filevisitalt, /* ^V */ + filewrite, /* ^W */ + swapmark /* ^X */ +}; + +static PF cXlp[] = { + definemacro, /* ( */ + finishmacro /* ) */ +}; + +static PF cX0[] = { + delwind, /* 0 */ + onlywind, /* 1 */ + splitwind, /* 2 */ + rescan, /* 3 */ + NULL /* 4 */ +}; + +static PF cXeq[] = { + showcpos /* = */ +}; + +static PF cXcar[] = { + enlargewind, /* ^ */ + rescan, /* _ */ + next_error, /* ` */ + rescan, /* a */ + usebuffer, /* b */ + rescan, /* c */ + rescan, /* d */ + executemacro, /* e */ + setfillcol, /* f */ + gotoline, /* g */ + markbuffer, /* h */ + fileinsert, /* i */ + rescan, /* j */ + killbuffer_cmd, /* k */ + rescan, /* l */ + rescan, /* m */ + nextwind, /* n */ + nextwind, /* o */ + prevwind, /* p */ + rescan, /* q */ + rescan, /* r */ + savebuffers, /* s */ + rescan, /* t */ + undo /* u */ +}; + +struct KEYMAPE (6 + IMAPEXT) cXmap = { + 6, + 6 + IMAPEXT, + rescan, + { + { + CCHR('B'), CCHR('G'), cXcB, NULL + }, + { + CCHR('L'), CCHR('X'), cXcL, NULL + }, + { + '(', ')', cXlp, NULL + }, + { + '0', '4', cX0, (KEYMAP *) & cX4map + }, + { + '=', '=', cXeq, NULL + }, + { + '^', 'u', cXcar, NULL + } + } +}; + +static PF metacG[] = { + ctrlg /* ^G */ +}; + +static PF metacV[] = { + pagenext /* ^V */ +}; + +static PF metasp[] = { + justone /* space */ +}; + +static PF metapct[] = { + queryrepl /* % */ +}; + +static PF metami[] = { + poptag, /* * */ + rescan, /* + */ + rescan, /* , */ + negative_argument, /* - */ + findtag, /* . */ + rescan, /* / */ + digit_argument, /* 0 */ + digit_argument, /* 1 */ + digit_argument, /* 2 */ + digit_argument, /* 3 */ + digit_argument, /* 4 */ + digit_argument, /* 5 */ + digit_argument, /* 6 */ + digit_argument, /* 7 */ + digit_argument, /* 8 */ + digit_argument, /* 9 */ + rescan, /* : */ + rescan, /* ; */ + gotobob, /* < */ + rescan, /* = */ + gotoeob /* > */ +}; + +static PF metasqf[] = { + NULL, /* [ */ + delwhite, /* \ */ + rescan, /* ] */ + joinline, /* ^ */ + rescan, /* _ */ + rescan, /* ` */ + rescan, /* a */ + backword, /* b */ + capword, /* c */ + delfword, /* d */ + rescan, /* e */ + forwword /* f */ +}; + +static PF metal[] = { + lowerword, /* l */ + backtoindent, /* m */ + rescan, /* n */ + rescan, /* o */ + rescan, /* p */ + fillpara, /* q */ + backsearch, /* r */ + forwsearch, /* s */ + rescan, /* t */ + upperword, /* u */ + backpage, /* v */ + copyregion, /* w */ + extend, /* x */ + rescan, /* y */ + rescan, /* z */ + gotobop, /* { */ + piperegion, /* | */ + gotoeop /* } */ +}; + +static PF metasqlZ[] = { + rescan /* Z */ +}; + +static PF metatilde[] = { + notmodified, /* ~ */ + delbword /* DEL */ +}; + +struct KEYMAPE (1 + IMAPEXT) metasqlmap = { + 1, + 1 + IMAPEXT, + rescan, + { + { + 'Z', 'Z', metasqlZ, NULL + } + } +}; + +struct KEYMAPE (8 + IMAPEXT) metamap = { + 8, + 8 + IMAPEXT, + rescan, + { + { + CCHR('G'), CCHR('G'), metacG, NULL + }, + { + CCHR('V'), CCHR('V'), metacV, NULL + }, + { + ' ', ' ', metasp, NULL + }, + { + '%', '%', metapct, NULL + }, + { + '*', '>', metami, NULL + }, + { + '[', 'f', metasqf, (KEYMAP *) &metasqlmap + }, + { + 'l', '}', metal, NULL + }, + { + '~', CCHR('?'), metatilde, NULL + } + } +}; + +static PF fund_at[] = { + setmark, /* ^@ */ + gotobol, /* ^A */ + backchar, /* ^B */ + NULL, /* ^C */ + forwdel, /* ^D */ + gotoeol, /* ^E */ + forwchar, /* ^F */ + ctrlg, /* ^G */ +}; + +static PF fund_h[] = { + NULL, /* ^H */ +}; + + +/* ^I is selfinsert */ +static PF fund_CJ[] = { + lfindent, /* ^J */ + killline, /* ^K */ + reposition, /* ^L */ + newline, /* ^M */ + forwline, /* ^N */ + openline, /* ^O */ + backline, /* ^P */ + quote, /* ^Q */ + backisearch, /* ^R */ + forwisearch, /* ^S */ + twiddle, /* ^T */ + universal_argument, /* ^U */ + forwpage, /* ^V */ + killregion, /* ^W */ + NULL, /* ^X */ + yank, /* ^Y */ + spawncli /* ^Z */ +}; + +static PF fund_esc[] = { + NULL, /* esc */ + rescan, /* ^\ selfinsert is default on fundamental */ + rescan, /* ^] */ + rescan, /* ^^ */ + undo /* ^_ */ +}; + +static PF fund_del[] = { + backdel /* DEL */ +}; + +static PF fund_cb[] = { + showmatch /* ) */ +}; + +#ifndef FUND_XMAPS +#define NFUND_XMAPS 0 /* extra map sections after normal ones */ +#endif + +static struct KEYMAPE (6 + NFUND_XMAPS + IMAPEXT) fundmap = { + 6 + NFUND_XMAPS, + 6 + NFUND_XMAPS + IMAPEXT, + selfinsert, + { + { + CCHR('@'), CCHR('G'), fund_at, (KEYMAP *) & ccmap + }, + { + CCHR('H'), CCHR('H'), fund_h, (KEYMAP *) & helpmap + }, + { + CCHR('J'), CCHR('Z'), fund_CJ, (KEYMAP *) & cXmap + }, + { + CCHR('['), CCHR('_'), fund_esc, (KEYMAP *) & metamap + }, + { + ')', ')', fund_cb, NULL + }, + { + CCHR('?'), CCHR('?'), fund_del, NULL + }, +#ifdef FUND_XMAPS + FUND_XMAPS, +#endif /* FUND_XMAPS */ + } +}; + +static PF fill_sp[] = { + fillword /* ' ' */ +}; + +static struct KEYMAPE (1 + IMAPEXT) fillmap = { + 1, + 1 + IMAPEXT, + rescan, + { + { ' ', ' ', fill_sp, NULL } + } +}; + +static PF indent_lf[] = { + newline, /* ^J */ + rescan, /* ^K */ + rescan, /* ^L */ + lfindent /* ^M */ +}; + +static struct KEYMAPE (1 + IMAPEXT) indntmap = { + 1, + 1 + IMAPEXT, + rescan, + { + { + CCHR('J'), CCHR('M'), indent_lf, NULL + } + } +}; + +#ifdef NOTAB +static PF notab_tab[] = { + space_to_tabstop /* ^I */ +}; + +static struct KEYMAPE (1 + IMAPEXT) notabmap = { + 1, + 1 + IMAPEXT, + rescan, + { + { + CCHR('I'), CCHR('I'), notab_tab, NULL + } + } +}; +#endif /* NOTAB */ + +static struct KEYMAPE (1 + IMAPEXT) overwmap = { + 0, + 1 + IMAPEXT, /* 1 to avoid 0 sized array */ + rescan, + { + /* unused dummy entry for VMS C */ + { + (KCHAR)0, (KCHAR)0, NULL, NULL + } + } +}; + + +/* + * The basic (root) keyboard map + */ +struct maps_s fundamental_mode = { (KEYMAP *)&fundmap, "fundamental" }; + +/* + * give names to the maps, for use by help etc. If the map is to be bindable, + * it must also be listed in the function name table below with the same + * name. Maps created dynamically currently don't get added here, thus are + * unnamed. Modes are just named keymaps with functions to add/subtract them + * from a buffer's list of modes. If you change a mode name, change it in + * modes.c also. + */ + +static struct maps_s map_table[] = { + {(KEYMAP *) &fillmap, "fill",}, + {(KEYMAP *) &indntmap, "indent",}, +#ifdef NOTAB + {(KEYMAP *) ¬abmap, "notab",}, +#endif /* NOTAB */ + {(KEYMAP *) &overwmap, "overwrite",}, + {(KEYMAP *) &metamap, "esc prefix",}, + {(KEYMAP *) &cXmap, "c-x prefix",}, + {(KEYMAP *) &cX4map, "c-x 4 prefix",}, + {(KEYMAP *) &helpmap, "help",}, + {NULL, NULL} +}; + +struct maps_s *maps; + +void +maps_init(void) +{ + int i; + struct maps_s *mp; + + maps = &fundamental_mode; + for (i = 0; map_table[i].p_name != NULL; i++) { + mp = &map_table[i]; + mp->p_next = maps; + maps = mp; + } +} + +/* + * Insert a new (named) keymap at the head of the keymap list. + */ +int +maps_add(KEYMAP *map, const char *name) +{ + struct maps_s *mp; + + if ((mp = malloc(sizeof(*mp))) == NULL) + return (FALSE); + + mp->p_name = name; + mp->p_map = map; + mp->p_next = maps; + maps = mp; + + return (TRUE); +} + +struct maps_s * +name_mode(const char *name) +{ + struct maps_s *mp; + + for (mp = maps; mp != NULL; mp = mp->p_next) + if (strcmp(mp->p_name, name) == 0) + return (mp); + return (NULL); +} + +KEYMAP * +name_map(const char *name) +{ + struct maps_s *mp; + + return ((mp = name_mode(name)) == NULL ? NULL : mp->p_map); +} diff --git a/mg/line.c b/mg/line.c new file mode 100644 index 0000000..bafd64f --- /dev/null +++ b/mg/line.c @@ -0,0 +1,635 @@ +/* $OpenBSD: line.c,v 1.50 2011/01/18 16:28:00 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * Text line handling. + * + * The functions in this file are a general set of line management + * utilities. They are the only routines that touch the text. They + * also touch the buffer and window structures to make sure that the + * necessary updating gets done. There are routines in this file that + * handle the kill buffer too. It isn't here for any good reason. + * + * Note that this code only updates the dot and mark values in the window + * list. Since all the code acts on the current window, the buffer that + * we are editing must be displayed, which means that "b_nwnd" is non-zero, + * which means that the dot and mark values in the buffer headers are + * nonsense. + */ + +#include "def.h" + +#include +#include + +/* + * Allocate a new line of size `used'. lrealloc() can be called if the line + * ever needs to grow beyond that. + */ +struct line * +lalloc(int used) +{ + struct line *lp; + + if ((lp = malloc(sizeof(*lp))) == NULL) + return (NULL); + lp->l_text = NULL; + lp->l_size = 0; + lp->l_used = used; /* XXX */ + if (lrealloc(lp, used) == FALSE) { + free(lp); + return (NULL); + } + return (lp); +} + +int +lrealloc(struct line *lp, int newsize) +{ + char *tmp; + + if (lp->l_size < newsize) { + if ((tmp = realloc(lp->l_text, newsize)) == NULL) + return (FALSE); + lp->l_text = tmp; + lp->l_size = newsize; + } + return (TRUE); +} + +/* + * Delete line "lp". Fix all of the links that might point to it (they are + * moved to offset 0 of the next line. Unlink the line from whatever buffer + * it might be in, and release the memory. The buffers are updated too; the + * magic conditions described in the above comments don't hold here. + */ +void +lfree(struct line *lp) +{ + struct buffer *bp; + struct mgwin *wp; + + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_linep == lp) + wp->w_linep = lp->l_fp; + if (wp->w_dotp == lp) { + wp->w_dotp = lp->l_fp; + wp->w_doto = 0; + } + if (wp->w_markp == lp) { + wp->w_markp = lp->l_fp; + wp->w_marko = 0; + } + } + for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { + if (bp->b_nwnd == 0) { + if (bp->b_dotp == lp) { + bp->b_dotp = lp->l_fp; + bp->b_doto = 0; + } + if (bp->b_markp == lp) { + bp->b_markp = lp->l_fp; + bp->b_marko = 0; + } + } + } + lp->l_bp->l_fp = lp->l_fp; + lp->l_fp->l_bp = lp->l_bp; + if (lp->l_text != NULL) + free(lp->l_text); + free(lp); +} + +/* + * This routine is called when a character changes in place in the current + * buffer. It updates all of the required flags in the buffer and window + * system. The flag used is passed as an argument; if the buffer is being + * displayed in more than 1 window we change EDIT to HARD. Set MODE if the + * mode line needs to be updated (the "*" has to be set). + */ +void +lchange(int flag) +{ + struct mgwin *wp; + + /* update mode lines if this is the first change. */ + if ((curbp->b_flag & BFCHG) == 0) { + flag |= WFMODE; + curbp->b_flag |= BFCHG; + } + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_bufp == curbp) { + wp->w_rflag |= flag; + if (wp != curwp) + wp->w_rflag |= WFFULL; + } + } +} + +/* + * Insert "n" bytes from "s" at the current location of dot. + * In the easy case all that happens is the text is stored in the line. + * In the hard case, the line has to be reallocated. When the window list + * is updated, take special care; I screwed it up once. You always update + * dot in the current window. You update mark and a dot in another window + * if it is greater than the place where you did the insert. Return TRUE + * if all is well, and FALSE on errors. + */ +int +linsert_str(const char *s, int n) +{ + struct line *lp1; + struct mgwin *wp; + RSIZE i; + int doto, k; + + if ((k = checkdirty(curbp)) != TRUE) + return (k); + + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read only"); + return (FALSE); + } + + if (!n) + return (TRUE); + + lchange(WFFULL); + + /* current line */ + lp1 = curwp->w_dotp; + + /* special case for the end */ + if (lp1 == curbp->b_headp) { + struct line *lp2, *lp3; + + /* now should only happen in empty buffer */ + if (curwp->w_doto != 0) + panic("bug: linsert_str"); + /* allocate a new line */ + if ((lp2 = lalloc(n)) == NULL) + return (FALSE); + /* previous line */ + lp3 = lp1->l_bp; + /* link in */ + lp3->l_fp = lp2; + lp2->l_fp = lp1; + lp1->l_bp = lp2; + lp2->l_bp = lp3; + for (i = 0; i < n; ++i) + lp2->l_text[i] = s[i]; + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_linep == lp1) + wp->w_linep = lp2; + if (wp->w_dotp == lp1) + wp->w_dotp = lp2; + if (wp->w_markp == lp1) + wp->w_markp = lp2; + } + undo_add_insert(lp2, 0, n); + curwp->w_doto = n; + return (TRUE); + } + /* save for later */ + doto = curwp->w_doto; + + if ((lp1->l_used + n) > lp1->l_size) { + if (lrealloc(lp1, lp1->l_used + n) == FALSE) + return (FALSE); + } + lp1->l_used += n; + if (lp1->l_used != n) + memmove(&lp1->l_text[doto + n], &lp1->l_text[doto], + lp1->l_used - n - doto); + + /* Add the characters */ + for (i = 0; i < n; ++i) + lp1->l_text[doto + i] = s[i]; + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_dotp == lp1) { + if (wp == curwp || wp->w_doto > doto) + wp->w_doto += n; + } + if (wp->w_markp == lp1) { + if (wp->w_marko > doto) + wp->w_marko += n; + } + } + undo_add_insert(curwp->w_dotp, doto, n); + return (TRUE); +} + +/* + * Insert "n" copies of the character "c" at the current location of dot. + * In the easy case all that happens is the text is stored in the line. + * In the hard case, the line has to be reallocated. When the window list + * is updated, take special care; I screwed it up once. You always update + * dot in the current window. You update mark and a dot in another window + * if it is greater than the place where you did the insert. Return TRUE + * if all is well, and FALSE on errors. + */ +int +linsert(int n, int c) +{ + struct line *lp1; + struct mgwin *wp; + RSIZE i; + int doto; + int s; + + if (!n) + return (TRUE); + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read only"); + return (FALSE); + } + + lchange(WFEDIT); + + /* current line */ + lp1 = curwp->w_dotp; + + /* special case for the end */ + if (lp1 == curbp->b_headp) { + struct line *lp2, *lp3; + + /* now should only happen in empty buffer */ + if (curwp->w_doto != 0) { + ewprintf("bug: linsert"); + return (FALSE); + } + /* allocate a new line */ + if ((lp2 = lalloc(n)) == NULL) + return (FALSE); + /* previous line */ + lp3 = lp1->l_bp; + /* link in */ + lp3->l_fp = lp2; + lp2->l_fp = lp1; + lp1->l_bp = lp2; + lp2->l_bp = lp3; + for (i = 0; i < n; ++i) + lp2->l_text[i] = c; + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_linep == lp1) + wp->w_linep = lp2; + if (wp->w_dotp == lp1) + wp->w_dotp = lp2; + if (wp->w_markp == lp1) + wp->w_markp = lp2; + } + undo_add_insert(lp2, 0, n); + curwp->w_doto = n; + return (TRUE); + } + /* save for later */ + doto = curwp->w_doto; + + if ((lp1->l_used + n) > lp1->l_size) { + if (lrealloc(lp1, lp1->l_used + n) == FALSE) + return (FALSE); + } + lp1->l_used += n; + if (lp1->l_used != n) + memmove(&lp1->l_text[doto + n], &lp1->l_text[doto], + lp1->l_used - n - doto); + + /* Add the characters */ + for (i = 0; i < n; ++i) + lp1->l_text[doto + i] = c; + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_dotp == lp1) { + if (wp == curwp || wp->w_doto > doto) + wp->w_doto += n; + } + if (wp->w_markp == lp1) { + if (wp->w_marko > doto) + wp->w_marko += n; + } + } + undo_add_insert(curwp->w_dotp, doto, n); + return (TRUE); +} + +/* + * Do the work of inserting a newline at the given line/offset. + * If mark is on the current line, we may have to move the markline + * to keep line numbers in sync. + * lnewline_at assumes the current buffer is writable. Checking for + * this fact should be done by the caller. + */ +int +lnewline_at(struct line *lp1, int doto) +{ + struct line *lp2; + int nlen; + struct mgwin *wp; + + lchange(WFFULL); + + curwp->w_bufp->b_lines++; + /* Check if mark is past dot (even on current line) */ + if (curwp->w_markline > curwp->w_dotline || + (curwp->w_dotline == curwp->w_markline && + curwp->w_marko >= doto)) + curwp->w_markline++; + curwp->w_dotline++; + + /* If start of line, allocate a new line instead of copying */ + if (doto == 0) { + /* new first part */ + if ((lp2 = lalloc(0)) == NULL) + return (FALSE); + lp2->l_bp = lp1->l_bp; + lp1->l_bp->l_fp = lp2; + lp2->l_fp = lp1; + lp1->l_bp = lp2; + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) + if (wp->w_linep == lp1) + wp->w_linep = lp2; + undo_add_boundary(FFRAND, 1); + undo_add_insert(lp2, 0, 1); + undo_add_boundary(FFRAND, 1); + return (TRUE); + } + + /* length of new part */ + nlen = llength(lp1) - doto; + + /* new second half line */ + if ((lp2 = lalloc(nlen)) == NULL) + return (FALSE); + if (nlen != 0) + bcopy(&lp1->l_text[doto], &lp2->l_text[0], nlen); + lp1->l_used = doto; + lp2->l_bp = lp1; + lp2->l_fp = lp1->l_fp; + lp1->l_fp = lp2; + lp2->l_fp->l_bp = lp2; + /* Windows */ + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_dotp == lp1 && wp->w_doto >= doto) { + wp->w_dotp = lp2; + wp->w_doto -= doto; + } + if (wp->w_markp == lp1 && wp->w_marko >= doto) { + wp->w_markp = lp2; + wp->w_marko -= doto; + } + } + undo_add_boundary(FFRAND, 1); + undo_add_insert(lp1, llength(lp1), 1); + undo_add_boundary(FFRAND, 1); + return (TRUE); +} + +/* + * Insert a newline into the buffer at the current location of dot in the + * current window. + */ +int +lnewline(void) +{ + int s; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read only"); + return (FALSE); + } + return (lnewline_at(curwp->w_dotp, curwp->w_doto)); +} + +/* + * This function deletes "n" bytes, starting at dot. (actually, n+1, as the + * newline is included) It understands how to deal with end of lines, etc. + * It returns TRUE if all of the characters were deleted, and FALSE if + * they were not (because dot ran into the end of the buffer). + * The "kflag" indicates either no insertion, or direction of insertion + * into the kill buffer. + */ +int +ldelete(RSIZE n, int kflag) +{ + struct line *dotp; + RSIZE chunk; + struct mgwin *wp; + int doto; + char *cp1, *cp2; + size_t len; + char *sv = NULL; + int end; + int s; + int rval = FALSE; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read only"); + goto out; + } + len = n; + if ((sv = calloc(1, len + 1)) == NULL) + goto out; + end = 0; + + undo_add_delete(curwp->w_dotp, curwp->w_doto, n, (kflag & KREG)); + + while (n != 0) { + dotp = curwp->w_dotp; + doto = curwp->w_doto; + /* Hit the end of the buffer */ + if (dotp == curbp->b_headp) + goto out; + /* Size of the chunk */ + chunk = dotp->l_used - doto; + + if (chunk > n) + chunk = n; + /* End of line, merge */ + if (chunk == 0) { + if (dotp == blastlp(curbp)) + goto out; + lchange(WFFULL); + if (ldelnewline() == FALSE) + goto out; + end = strlcat(sv, "\n", len + 1); + --n; + continue; + } + lchange(WFEDIT); + /* Scrunch text */ + cp1 = &dotp->l_text[doto]; + memcpy(&sv[end], cp1, chunk); + end += chunk; + sv[end] = '\0'; + for (cp2 = cp1 + chunk; cp2 < &dotp->l_text[dotp->l_used]; + cp2++) + *cp1++ = *cp2; + dotp->l_used -= (int)chunk; + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_dotp == dotp && wp->w_doto >= doto) { + /* NOSTRICT */ + wp->w_doto -= chunk; + if (wp->w_doto < doto) + wp->w_doto = doto; + } + if (wp->w_markp == dotp && wp->w_marko >= doto) { + /* NOSTRICT */ + wp->w_marko -= chunk; + if (wp->w_marko < doto) + wp->w_marko = doto; + } + } + n -= chunk; + } + if (kchunk(sv, (RSIZE)len, kflag) != TRUE) + goto out; + rval = TRUE; +out: + free(sv); + return (rval); +} + +/* + * Delete a newline and join the current line with the next line. If the next + * line is the magic header line always return TRUE; merging the last line + * with the header line can be thought of as always being a successful + * operation. Even if nothing is done, this makes the kill buffer work + * "right". If the mark is past the dot (actually, markline > dotline), + * decrease the markline accordingly to keep line numbers in sync. + * Easy cases can be done by shuffling data around. Hard cases + * require that lines be moved about in memory. Return FALSE on error and + * TRUE if all looks ok. We do not update w_dotline here, as deletes are done + * after moves. + */ +int +ldelnewline(void) +{ + struct line *lp1, *lp2, *lp3; + struct mgwin *wp; + int s; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read only"); + return (FALSE); + } + + lp1 = curwp->w_dotp; + lp2 = lp1->l_fp; + /* at the end of the buffer */ + if (lp2 == curbp->b_headp) + return (TRUE); + /* Keep line counts in sync */ + curwp->w_bufp->b_lines--; + if (curwp->w_markline > curwp->w_dotline) + curwp->w_markline--; + if (lp2->l_used <= lp1->l_size - lp1->l_used) { + bcopy(&lp2->l_text[0], &lp1->l_text[lp1->l_used], lp2->l_used); + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_linep == lp2) + wp->w_linep = lp1; + if (wp->w_dotp == lp2) { + wp->w_dotp = lp1; + wp->w_doto += lp1->l_used; + } + if (wp->w_markp == lp2) { + wp->w_markp = lp1; + wp->w_marko += lp1->l_used; + } + } + lp1->l_used += lp2->l_used; + lp1->l_fp = lp2->l_fp; + lp2->l_fp->l_bp = lp1; + free(lp2); + return (TRUE); + } + if ((lp3 = lalloc(lp1->l_used + lp2->l_used)) == NULL) + return (FALSE); + bcopy(&lp1->l_text[0], &lp3->l_text[0], lp1->l_used); + bcopy(&lp2->l_text[0], &lp3->l_text[lp1->l_used], lp2->l_used); + lp1->l_bp->l_fp = lp3; + lp3->l_fp = lp2->l_fp; + lp2->l_fp->l_bp = lp3; + lp3->l_bp = lp1->l_bp; + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_linep == lp1 || wp->w_linep == lp2) + wp->w_linep = lp3; + if (wp->w_dotp == lp1) + wp->w_dotp = lp3; + else if (wp->w_dotp == lp2) { + wp->w_dotp = lp3; + wp->w_doto += lp1->l_used; + } + if (wp->w_markp == lp1) + wp->w_markp = lp3; + else if (wp->w_markp == lp2) { + wp->w_markp = lp3; + wp->w_marko += lp1->l_used; + } + } + free(lp1); + free(lp2); + return (TRUE); +} + +/* + * Replace plen characters before dot with argument string. Control-J + * characters in st are interpreted as newlines. There is a casehack + * disable flag (normally it likes to match case of replacement to what + * was there). + */ +int +lreplace(RSIZE plen, char *st) +{ + RSIZE rlen; /* replacement length */ + int s; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read only"); + return (FALSE); + } + undo_boundary_enable(FFRAND, 0); + + (void)backchar(FFARG | FFRAND, (int)plen); + (void)ldelete(plen, KNONE); + + rlen = strlen(st); + region_put_data(st, rlen); + lchange(WFFULL); + + undo_boundary_enable(FFRAND, 1); + return (TRUE); +} + +/* + * Allocate and return the supplied line as a C string + */ +char * +linetostr(const struct line *ln) +{ + int len; + char *line; + + len = llength(ln); + if (len == INT_MAX) /* (len + 1) overflow */ + return (NULL); + + if ((line = malloc(len + 1)) == NULL) + return (NULL); + + (void)memcpy(line, ltext(ln), len); + line[len] = '\0'; + + return (line); +} diff --git a/mg/macro.c b/mg/macro.c new file mode 100644 index 0000000..1910478 --- /dev/null +++ b/mg/macro.c @@ -0,0 +1,106 @@ +/* $OpenBSD: macro.c,v 1.14 2012/04/12 04:47:59 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Keyboard macros. + */ + +#include "def.h" +#include "key.h" +#include "macro.h" + +int inmacro = FALSE; /* Macro playback in progess */ +int macrodef = FALSE; /* Macro recording in progress */ +int macrocount = 0; + +struct line *maclhead = NULL; +struct line *maclcur; + +union macrodef macro[MAXMACRO]; + +/* ARGSUSED */ +int +definemacro(int f, int n) +{ + struct line *lp1, *lp2; + + macrocount = 0; + + if (macrodef) { + ewprintf("already defining macro"); + return (macrodef = FALSE); + } + + /* free lines allocated for string arguments */ + if (maclhead != NULL) { + for (lp1 = maclhead->l_fp; lp1 != maclhead; lp1 = lp2) { + lp2 = lp1->l_fp; + free(lp1); + } + free(lp1); + } + + if ((maclhead = lp1 = lalloc(0)) == NULL) + return (FALSE); + + ewprintf("Defining Keyboard Macro..."); + maclcur = lp1->l_fp = lp1->l_bp = lp1; + return (macrodef = TRUE); +} + +/* ARGSUSED */ +int +finishmacro(int f, int n) +{ + if (macrodef == TRUE) { + macrodef = FALSE; + ewprintf("End Keyboard Macro Definition"); + return (TRUE); + } + return (FALSE); +} + +/* ARGSUSED */ +int +executemacro(int f, int n) +{ + int i, j, flag, num; + PF funct; + + if (macrodef || + (macrocount >= MAXMACRO && macro[MAXMACRO - 1].m_funct + != finishmacro)) { + ewprintf("Macro too long. Aborting."); + return (FALSE); + } + + if (macrocount == 0) + return (TRUE); + + inmacro = TRUE; + + for (i = n; i > 0; i--) { + maclcur = maclhead->l_fp; + flag = 0; + num = 1; + for (j = 0; j < macrocount - 1; j++) { + funct = macro[j].m_funct; + if (funct == universal_argument) { + flag = FFARG; + num = macro[++j].m_count; + continue; + } + if ((*funct)(flag, num) != TRUE) { + inmacro = FALSE; + return (FALSE); + } + lastflag = thisflag; + thisflag = 0; + flag = 0; + num = 1; + } + } + inmacro = FALSE; + return (TRUE); +} diff --git a/mg/macro.h b/mg/macro.h new file mode 100644 index 0000000..52cc83d --- /dev/null +++ b/mg/macro.h @@ -0,0 +1,21 @@ +/* $OpenBSD: macro.h,v 1.7 2005/11/18 20:56:53 deraadt Exp $ */ + +/* This file is in the public domain. */ + +/* definitions for keyboard macros */ + +#define MAXMACRO 256 /* maximum functs in a macro */ + +extern int inmacro; +extern int macrodef; +extern int macrocount; + +union macrodef { + PF m_funct; + int m_count; /* for count-prefix */ +}; + +extern union macrodef macro[MAXMACRO]; + +extern struct line *maclhead; +extern struct line *maclcur; diff --git a/mg/main.c b/mg/main.c new file mode 100644 index 0000000..c93eb9f --- /dev/null +++ b/mg/main.c @@ -0,0 +1,245 @@ +/* $OpenBSD: main.c,v 1.67 2012/05/29 06:08:48 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Mainline. + */ + +#include "def.h" +#include "kbd.h" +#include "funmap.h" +#include "macro.h" + +#include + +int thisflag; /* flags, this command */ +int lastflag; /* flags, last command */ +int curgoal; /* goal column */ +int startrow; /* row to start */ +struct buffer *curbp; /* current buffer */ +struct buffer *bheadp; /* BUFFER list head */ +struct mgwin *curwp; /* current window */ +struct mgwin *wheadp; /* MGWIN listhead */ +char pat[NPAT]; /* pattern */ + +static void edinit(struct buffer *); +static __dead void usage(void); + +extern char *__progname; +extern void closetags(void); + +static __dead void +usage() +{ + fprintf(stderr, "usage: %s [-n] [-f mode] [+number] [file ...]\n", + __progname); + exit(1); +} + +int +main(int argc, char **argv) +{ + char *cp, *init_fcn_name = NULL; + PF init_fcn = NULL; + int o, i, nfiles; + int nobackups = 0; + struct buffer *bp = NULL; + + while ((o = getopt(argc, argv, "nf:")) != -1) + switch (o) { + case 'n': + nobackups = 1; + break; + case 'f': + if (init_fcn_name != NULL) + errx(1, "cannot specify more than one " + "initial function"); + init_fcn_name = optarg; + break; + default: + usage(); + } + argc -= optind; + argv += optind; + + maps_init(); /* Keymaps and modes. */ + funmap_init(); /* Functions. */ + + /* + * This is where we initialize standalone extensions that should + * be loaded dynamically sometime in the future. + */ + { + extern void grep_init(void); + extern void theo_init(void); + extern void cmode_init(void); + extern void dired_init(void); + + dired_init(); + grep_init(); + theo_init(); + cmode_init(); + } + + if (init_fcn_name && + (init_fcn = name_function(init_fcn_name)) == NULL) + errx(1, "Unknown function `%s'", init_fcn_name); + + vtinit(); /* Virtual terminal. */ + dirinit(); /* Get current directory. */ + edinit(bp); /* Buffers, windows. */ + ttykeymapinit(); /* Symbols, bindings. */ + + /* + * doing update() before reading files causes the error messages from + * the file I/O show up on the screen. (and also an extra display of + * the mode line if there are files specified on the command line.) + */ + update(); + + /* user startup file. */ + if ((cp = startupfile(NULL)) != NULL) + (void)load(cp); + + /* + * Now ensure any default buffer modes from the startup file are + * given to any files opened when parsing the startup file. + * Note *scratch* will also be updated. + */ + for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { + bp->b_flag = defb_flag; + for (i = 0; i <= defb_nmodes; i++) { + bp->b_modes[i] = defb_modes[i]; + } + } + + /* Force FFOTHARG=1 so that this mode is enabled, not simply toggled */ + if (init_fcn) + init_fcn(FFOTHARG, 1); + + if (nobackups) + makebkfile(FFARG, 0); + + for (nfiles = 0, i = 0; i < argc; i++) { + if (argv[i][0] == '+' && strlen(argv[i]) >= 2) { + long long lval; + const char *errstr; + + lval = strtonum(&argv[i][1], INT_MIN, INT_MAX, &errstr); + if (argv[i][1] == '\0' || errstr != NULL) + goto notnum; + startrow = lval; + } else { +notnum: + cp = adjustname(argv[i], FALSE); + if (cp != NULL) { + if (nfiles == 1) + splitwind(0, 1); + + if ((curbp = findbuffer(cp)) == NULL) { + vttidy(); + errx(1, "Can't find current buffer!"); + } + (void)showbuffer(curbp, curwp, 0); + if (readin(cp) != TRUE) + killbuffer(curbp); + else { + /* Ensure enabled, not just toggled */ + if (init_fcn_name) + init_fcn(FFOTHARG, 1); + nfiles++; + } + } + } + } + + if (nfiles > 2) + listbuffers(0, 1); + + /* fake last flags */ + thisflag = 0; + for (;;) { + if (epresf == KCLEAR) + eerase(); + if (epresf == TRUE) + epresf = KCLEAR; + if (winch_flag) { + do_redraw(0, 0, TRUE); + winch_flag = 0; + } + update(); + lastflag = thisflag; + thisflag = 0; + + switch (doin()) { + case TRUE: + break; + case ABORT: + ewprintf("Quit"); + /* FALLTHRU */ + case FALSE: + default: + ttbeep(); + macrodef = FALSE; + } + } +} + +/* + * Initialize default buffer and window. Default buffer is called *scratch*. + */ +static void +edinit(struct buffer *bp) +{ + struct mgwin *wp; + + bheadp = NULL; + bp = bfind("*scratch*", TRUE); /* Text buffer. */ + if (bp == NULL) + panic("edinit"); + + wp = new_window(bp); + if (wp == NULL) + panic("edinit: Out of memory"); + + curbp = bp; /* Current buffer. */ + wheadp = wp; + curwp = wp; + wp->w_wndp = NULL; /* Initialize window. */ + wp->w_linep = wp->w_dotp = bp->b_headp; + wp->w_ntrows = nrow - 2; /* 2 = mode, echo. */ + wp->w_rflag = WFMODE | WFFULL; /* Full. */ +} + +/* + * Quit command. If an argument, always quit. Otherwise confirm if a buffer + * has been changed and not written out. Normally bound to "C-X C-C". + */ +/* ARGSUSED */ +int +quit(int f, int n) +{ + int s; + + if ((s = anycb(FALSE)) == ABORT) + return (ABORT); + if (s == FALSE + || eyesno("Modified buffers exist; really exit") == TRUE) { + vttidy(); + closetags(); + exit(GOOD); + } + return (TRUE); +} + +/* + * User abort. Should be called by any input routine that sees a C-g to abort + * whatever C-g is aborting these days. Currently does nothing. + */ +/* ARGSUSED */ +int +ctrlg(int f, int n) +{ + return (ABORT); +} diff --git a/mg/match.c b/mg/match.c new file mode 100644 index 0000000..4c5d0c3 --- /dev/null +++ b/mg/match.c @@ -0,0 +1,185 @@ +/* $OpenBSD: match.c,v 1.16 2009/06/04 02:23:37 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * Limited parenthesis matching routines + * + * The hacks in this file implement automatic matching * of (), [], {}, and + * other characters. It would be better to have a full-blown syntax table, + * but there's enough overhead in the editor as it is. + */ + +#include "def.h" +#include "key.h" + +static int balance(void); +static void displaymatch(struct line *, int); + +/* + * Balance table. When balance() encounters a character that is to be + * matched, it first searches this table for a balancing left-side character. + * If the character is not in the table, the character is balanced by itself. + */ +static struct balance { + char left, right; +} bal[] = { + { '(', ')' }, + { '[', ']' }, + { '{', '}' }, + { '<', '>' }, + { '\0', '\0' } +}; + +/* + * Hack to show matching paren. Self-insert character, then show matching + * character, if any. Bound to "blink-and-insert". + */ +int +showmatch(int f, int n) +{ + int i, s; + + for (i = 0; i < n; i++) { + if ((s = selfinsert(FFRAND, 1)) != TRUE) + return (s); + /* unbalanced -- warn user */ + if (balance() != TRUE) + ttbeep(); + } + return (TRUE); +} + +/* + * Search for and display a matching character. + * + * This routine does the real work of searching backward + * for a balancing character. If such a balancing character + * is found, it uses displaymatch() to display the match. + */ +static int +balance(void) +{ + struct line *clp; + int cbo; + int c, i, depth; + int rbal, lbal; + + rbal = key.k_chars[key.k_count - 1]; + + /* See if there is a matching character -- default to the same */ + lbal = rbal; + for (i = 0; bal[i].right != '\0'; i++) + if (bal[i].right == rbal) { + lbal = bal[i].left; + break; + } + + /* + * Move behind the inserted character. We are always guaranteed + * that there is at least one character on the line, since one was + * just self-inserted by blinkparen. + */ + clp = curwp->w_dotp; + cbo = curwp->w_doto - 1; + + /* init nesting depth */ + depth = 0; + + for (;;) { + if (cbo == 0) { + clp = lback(clp); /* beginning of line */ + if (clp == curbp->b_headp) + return (FALSE); + cbo = llength(clp) + 1; + } + if (--cbo == llength(clp)) + c = '\n'; /* end of line */ + else + c = lgetc(clp, cbo); /* somewhere in middle */ + + /* + * Check for a matching character. If still in a nested + * level, pop out of it and continue search. This check + * is done before the nesting check so single-character + * matches will work too. + */ + if (c == lbal) { + if (depth == 0) { + displaymatch(clp, cbo); + return (TRUE); + } else + depth--; + } + /* Check for another level of nesting. */ + if (c == rbal) + depth++; + } + /* NOTREACHED */ +} + +/* + * Display matching character. Matching characters that are not in the + * current window are displayed in the echo line. If in the current window, + * move dot to the matching character, sit there a while, then move back. + */ +static void +displaymatch(struct line *clp, int cbo) +{ + struct line *tlp; + int tbo; + int cp; + int bufo; + int c; + int inwindow; + char buf[NLINE]; + + /* + * Figure out if matching char is in current window by + * searching from the top of the window to dot. + */ + inwindow = FALSE; + for (tlp = curwp->w_linep; tlp != lforw(curwp->w_dotp); + tlp = lforw(tlp)) + if (tlp == clp) + inwindow = TRUE; + + if (inwindow == TRUE) { + tlp = curwp->w_dotp; /* save current position */ + tbo = curwp->w_doto; + + curwp->w_dotp = clp; /* move to new position */ + curwp->w_doto = cbo; + curwp->w_rflag |= WFMOVE; + + update(); /* show match */ + ttwait(1000); /* wait for key or 1 second */ + + curwp->w_dotp = tlp; /* return to old position */ + curwp->w_doto = tbo; + curwp->w_rflag |= WFMOVE; + update(); + } else { + /* match is not in this window, so display line in echo area */ + bufo = 0; + for (cp = 0; cp < llength(clp); cp++) { + c = lgetc(clp, cp); + if (c != '\t' +#ifdef NOTAB + || (curbp->b_flag & BFNOTAB) +#endif + ) + if (ISCTRL(c)) { + buf[bufo++] = '^'; + buf[bufo++] = CCHR(c); + } else + buf[bufo++] = c; + else + do { + buf[bufo++] = ' '; + } while (bufo & 7); + } + buf[bufo++] = '\0'; + ewprintf("Matches %s", buf); + } +} diff --git a/mg/mg.1 b/mg/mg.1 new file mode 100644 index 0000000..134af55 --- /dev/null +++ b/mg/mg.1 @@ -0,0 +1,956 @@ +.\" $OpenBSD: mg.1,v 1.68 2012/07/11 19:56:13 sobrado Exp $ +.\" This file is in the public domain. +.\" +.Dd $Mdocdate: July 11 2012 $ +.Dt MG 1 +.Os +.Sh NAME +.Nm mg +.Nd emacs-like text editor +.Sh SYNOPSIS +.Nm mg +.Op Fl n +.Op Fl f Ar mode +.Op + Ns Ar number +.Op Ar +.Sh DESCRIPTION +.Nm +is intended to be a small, fast, and portable editor for +people who can't (or don't want to) run emacs for one +reason or another, or are not familiar with the +.Xr vi 1 +editor. +It is compatible with emacs because there shouldn't +be any reason to learn more editor types than emacs or +.Xr vi 1 . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It + Ns Ar number +Go to the line specified by number (do not insert +a space between the +.Sq + +sign and the number). +If a negative number is specified, the line number counts +backwards from the end of the file i.e. +-1 will be the last +line of the file, +-2 will be second last, and so on. +.It Fl f Ar mode +Run the mode command for all buffers created from +arguments on the command line, including the +scratch buffer and all files. +.It Fl n +Turn off backup file generation. +.El +.Sh WINDOWS AND BUFFERS +When a file is loaded into +.Nm , +it is stored in a +.Em buffer . +This buffer may be displayed on the screen in more than one window. +At present, windows may only be split horizontally, so each window is +delineated by a modeline at the bottom. +If changes are made to a buffer, it will be reflected in all open windows. +.Pp +If a buffer name begins and ends with an asterisk, the buffer is considered +throwaway; i.e. the user will not be prompted to save changes when +the buffer is killed. +.Sh POINT AND MARK +The current cursor location in +.Nm +is called the +.Em point +(or +.Em dot ) . +It is possible to define a window-specific region of text by setting a second +location, called the +.Em mark . +The +.Em region +is the text between point and mark inclusive. +Deleting the character at the mark position leaves +the mark at the point of deletion. +.Pp +Note: The point and mark are window-specific in +.Nm , +not buffer-specific, as in other emacs flavours. +.Sh BACKUP FILES +Backup files have a +.Sq ~ +character appended to the file name and +are created in the current working directory by default. +Whether to create backup files or not can be toggled with the +make-backup-file command. +The backup file location can either be in the current +working directory, or all backups can be moved to a +.Pa ~/.mg.d +directory where files retain their path name to retain uniqueness. +Use the backup-to-home-directory to alternate between these two locations. +Further, if any application creates backup files in the +.Ev TMPDIR , +these can be left with the leave-tmpdir-backups command. +.Sh TAGS +.Nm +supports tag files created by +.Xr ctags 1 , +allowing the user to quickly locate various object definitions. +Note though that emacs uses etags, not ctags. +.Sh CSCOPE +.Nm +supports navigating source code using cscope. +However, +.Nm +requires cscope and cscope-indexer executables to be present in +.Ev PATH +for it to work. +.Sh DEFAULT KEY BINDINGS +Normal editing commands are very similar to GNU Emacs. +In the following examples, C-x means Control-x, and M-x means Meta-x, +where the Meta key may be either a special key on the keyboard +or the ALT key; otherwise ESC followed by the key X works as well. +.Pp +.Bl -tag -width xxxxxxxxxxxx -offset indent -compact +.It C-SPC +set-mark-command +.It C-a +beginning-of-line +.It C-b +backward-char +.It C-c s c +cscope-find-functions-calling-this-function +.It C-c s d +cscope-find-global-definition +.It C-c s e +cscope-find-egrep-pattern +.It C-c s f +cscope-find-this-file +.It C-c s i +cscope-find-files-including-file +.It C-c s n +cscope-next-symbol +.It C-c s p +cscope-prev-symbol +.It C-c s s +cscope-find-this-symbol +.It C-c s t +cscope-find-this-text-string +.It C-d +delete-char +.It C-e +end-of-line +.It C-f +forward-char +.It C-g +keyboard-quit +.It C-h C-h +help-help +.It C-h a +apropos +.It C-h b +describe-bindings +.It C-h c +describe-key-briefly +.It C-j +newline-and-indent +.It C-k +kill-line +.It C-l +recenter +.It RET +newline +.It C-n +next-line +.It C-o +open-line +.It C-p +previous-line +.It C-q +quoted-insert +.It C-r +isearch-backward +.It C-s +isearch-forward +.It C-t +transpose-chars +.It C-u +universal-argument +.It C-v +scroll-up +.It C-w +kill-region +.It C-x C-b +list-buffers +.It C-x C-c +save-buffers-kill-emacs +.It C-x C-f +find-file +.It C-x C-g +keyboard-quit +.It C-x C-l +downcase-region +.It C-x C-o +delete-blank-lines +.It C-x C-q +toggle-read-only +.It C-x C-r +find-file-read-only +.It C-x C-s +save-buffer +.It C-x C-u +upcase-region +.It C-x C-v +find-alternate-file +.It C-x C-w +write-file +.It C-x C-x +exchange-point-and-mark +.It C-x ( +start-kbd-macro +.It C-x \&) +end-kbd-macro +.It C-x 0 +delete-window +.It C-x 1 +delete-other-windows +.It C-x 2 +split-window-vertically +.It C-x 4 C-f +find-file-other-window +.It C-x 4 C-g +keyboard-quit +.It C-x 4 b +switch-to-buffer-other-window +.It C-x 4 f +find-file-other-window +.It C-x = +what-cursor-position +.It C-x ^ +enlarge-window +.It C-x ` +next-error +.It C-x b +switch-to-buffer +.It C-x d +dired +.It C-x e +call-last-kbd-macro +.It C-x f +set-fill-column +.It C-x g +goto-line +.It C-x h +mark-whole-buffer +.It C-x i +insert-file +.It C-x k +kill-buffer +.It C-x n +other-window +.It C-x o +other-window +.It C-x p +previous-window +.It C-x s +save-some-buffers +.It C-x u +undo +.It C-y +yank +.It C-z +suspend-emacs +.It M-C-v +scroll-other-window +.It M-SPC +just-one-space +.It M-. +find-tag +.It M-* +pop-tag-mark +.It M-% +query-replace +.It M-< +beginning-of-buffer +.It M-> +end-of-buffer +.It M-\e +delete-horizontal-space +.It M-^ +join-line +.It M-b +backward-word +.It M-c +capitalize-word +.It M-d +kill-word +.It M-f +forward-word +.It M-l +downcase-word +.It M-m +back-to-indentation +.It M-q +fill-paragraph +.It M-r +search-backward +.It M-s +search-forward +.It M-u +upcase-word +.It M-v +scroll-down +.It M-w +copy-region-as-kill +.It M-x +execute-extended-command +.It M-{ +backward-paragraph +.It M-| +shell-command-on-region +.It M-} +forward-paragraph +.It M-~ +not-modified +.It M-DEL +backward-kill-word +.It C-_ +undo +.It ) +blink-and-insert +.It DEL +delete-backward-char +.El +.Pp +For a complete description of +.Nm +commands, see +.Sx MG COMMANDS . +To see the active keybindings at any time, type +.Dq M-x describe-bindings . +.Sh MG COMMANDS +Commands are invoked by +.Dq M-x , +or by binding to a key. +Many commands take an optional numerical parameter, +.Va n . +This parameter is set either by +M- (where +.Va n +is the numerical argument) before the command, or by +one or more invocations of the universal argument, usually bound to C-u. +When invoked in this manner, the value of the numeric parameter to +be passed is displayed in the minibuffer before the M-x. +One common use of the parameter is in mode toggles (e.g.\& +make-backup-files). +If no parameter is supplied, the mode is toggled to its +alternate state. +If a positive parameter is supplied, the mode is forced to on. +Otherwise, it is forced to off. +.\" +.Bl -tag -width xxxxx +.It apropos +Help Apropos. +Prompt the user for a string, open the *help* buffer, +and list all +.Nm +commands that contain that string. +.It auto-execute +Register an auto-execute hook; that is, specify a filename pattern +(conforming to the shell's filename globbing rules) and an associated +function to execute when a file matching the specified pattern +is read into a buffer. +.It auto-fill-mode +Toggle auto-fill mode (sometimes called mail-mode), +where text inserted past the fill column is automatically wrapped +to a new line. +.It auto-indent-mode +Toggle indent mode, where indentation is preserved after a newline. +.It back-to-indentation +Move the dot to the first non-whitespace character on the current line. +.It backup-to-home-directory +Save backup copies to a +.Pa ~/.mg.d +directory instead of working directory. +Requires make-backup-files to be on. +.It backward-char +Move cursor backwards one character. +.It backward-kill-word +Kill text backwards by +.Va n +words. +.It backward-paragraph +Move cursor backwards +.Va n +paragraphs. +Paragraphs are delimited by or or . +.It backward-word +Move cursor backwards by the specified number of words. +.It beginning-of-buffer +Move cursor to the top of the buffer. +.It beginning-of-line +Move cursor to the beginning of the line. +.It blink-and-insert +Self-insert a character, then search backwards and blink its +matching delimiter. +For delimiters other than +parenthesis, brackets, and braces, the character itself +is used as its own match. +.It bsmap-mode +Toggle bsmap mode, where DEL and C-h are swapped. +.It c-mode +Toggle a KNF-compliant mode for editing C program files. +.It call-last-kbd-macro +Invoke the keyboard macro. +.It capitalize-word +Capitalize +.Va n +words; i.e. convert the first character of the word to +upper case, and subsequent letters to lower case. +.It cd +Change the global working directory. +See also global-wd-mode. +.It copy-region-as-kill +Copy all of the characters in the region to the kill buffer, +clearing the mark afterwards. +This is a bit like a kill-region followed by a yank. +.It count-matches +Count the number of lines matching the supplied regular expression. +.It count-non-matches +Count the number of lines not matching the supplied regular expression. +.It cscope-find-this-symbol +List the matches for the given symbol. +.It cscope-find-global-definition +List global definitions for the given literal. +.It cscope-find-called-functions +List functions called from the given function. +.It cscope-find-functions-calling-this-function +List functions calling the given function. +.It cscope-find-this-text-string +List locations matching the given text string. +.It cscope-find-egrep-pattern +List locations matching the given extended regular expression pattern. +.It cscope-find-this-file +List filenames matching the given filename. +.It cscope-find-files-including-file +List files that #include the given filename. +.It cscope-next-symbol +Navigate to the next match. +.It cscope-prev-symbol +Navigate to the previous match. +.It cscope-next-file +Navigate to the next file. +.It cscope-prev-file +Navigate to the previous file. +.It cscope-create-list-of-files-to-index +Create cscope's List and Index in the given directory. +.It define-key +Prompts the user for a named keymap (mode), +a key, and an +.Nm +command, then creates a keybinding in the appropriate +map. +.It delete-backward-char +Delete backwards +.Va n +characters. +Like delete-char, this actually does a kill if presented +with an argument. +.It delete-blank-lines +Delete blank lines around dot. +If dot is sitting on a blank line, this command +deletes all the blank lines above and below the current line. +Otherwise, it deletes all of the blank lines after the current line. +.It delete-char +Delete +.Va n +characters forward. +If any argument is present, it kills rather than deletes, +saving the result in the kill buffer. +.It delete-horizontal-space +Delete any whitespace around the dot. +.It delete-leading-space +Delete leading whitespace on the current line. +.It delete-trailing-space +Delete trailing whitespace on the current line. +.It delete-matching-lines +Delete all lines after dot that contain a string matching +the supplied regular expression. +.It delete-non-matching-lines +Delete all lines after dot that contain a string matching +the supplied regular expression. +.It delete-other-windows +Make the current window the only window visible on the screen. +.It delete-window +Delete current window. +.It describe-bindings +List all global and local keybindings, putting the result in +the *help* buffer. +.It describe-key-briefly +Read a key from the keyboard, and look it up in the keymap. +Display the name of the function currently bound to the key. +.It digit-argument +Process a numerical argument for keyboard-invoked functions. +.It downcase-region +Set all characters in the region to lower case. +.It downcase-word +Set characters to lower case, starting at the dot, and ending +.Va n +words away. +.It emacs-version +Return an +.Nm +version string. +.It end-kbd-macro +Stop defining a keyboard macro. +.It end-of-buffer +Move cursor to the end of the buffer. +.It end-of-line +Move cursor to the end of the line. +.It enlarge-window +Enlarge the current window by shrinking either the window above +or below it. +.It eval-current-buffer +Evaluate the current buffer as a series of +.Nm +commands. +Useful for testing +.Nm +startup files. +.It eval-expression +Get one line from the user, and run it. +Useful for testing expressions in +.Nm +startup files. +.It exchange-point-and-mark +Swap the values of "dot" and "mark" in the current window. +Return an error if no mark is set. +.It execute-extended-command +Invoke an extended command; i.e. M-x. +Call the message line routine to read in the command name and apply +autocompletion to it. +When it comes back, look the name up in the symbol table and run the +command if it is found, passing arguments as necessary. +Print an error if there is anything wrong. +.It fill-paragraph +Justify a paragraph, wrapping text at the current fill column. +.It find-file +Select a file for editing. +First check if the file can be found +in another buffer; if it is there, just switch to that buffer. +If the file cannot be found, create a new buffer, read in the +file from disk, and switch to the new buffer. +.It find-file-read-only +Same as find-file, except the new buffer is set to read-only. +.It find-alternate-file +Replace the current file with an alternate one. +Semantics for finding the replacement file are the same as +find-file, except the current buffer is killed before the switch. +If the kill fails, or is aborted, revert to the original file. +.It find-file-other-window +Opens the specified file in a second buffer. +Splits the current window if necessary. +.It find-tag +Jump to definition of tag at dot. +.It forward-char +Move cursor forwards (or backwards, if +.Va n +is negative) +.Va n +characters. +Returns an error if the end of buffer is reached. +.It forward-paragraph +Move forward +.Va n +paragraphs. +Paragraphs are delimited by or or . +.It forward-word +Move the cursor forward by the specified number of words. +.It global-set-key +Bind a key in the global (fundamental) key map. +.It global-unset-key +Unbind a key from the global (fundamental) key map; i.e. set it to 'rescan'. +.It global-wd-mode +Toggle global working-directory mode. +When enabled, +.Nm +defaults to opening files (and executing commands like compile and grep) +relative to the global working directory. +When disabled, a working directory is set for each buffer. +.It goto-line +Go to a specific line. +If an argument is present, then +it is the line number, else prompt for a line number to use. +.It help-help +Prompts for one of (a)propos, (b)indings, des(c)ribe key briefly. +.It insert +Insert a string, mainly for use from macros. +.It insert-buffer +Insert the contents of another buffer at dot. +.It insert-file +Insert a file into the current buffer at dot. +.It insert-with-wrap +Insert the bound character with word wrap. +Check to see if we're past the fill column, and if so, +justify this line. +.It isearch-backward +Use incremental searching, initially in the reverse direction. +isearch ignores any explicit arguments. +If invoked during macro definition or evaluation, the non-incremental +search-backward is invoked instead. +.It isearch-forward +Use incremental searching, initially in the forward direction. +isearch ignores any explicit arguments. +If invoked during macro definition or evaluation, the non-incremental +search-forward is invoked instead. +.It join-line +Join the current line to the previous. +If called with an argument, +join the next line to the current one. +.It just-one-space +Delete any whitespace around dot, then insert a space. +.It keyboard-quit +Abort the current action. +.It kill-buffer +Dispose of a buffer, by name. +If the buffer name does not start and end with an asterisk, +prompt the user if the buffer +has been changed. +.It kill-line +Kill line. +If called without an argument, it kills from dot to the end +of the line, unless it is at the end of the line, when it kills the +newline. +If called with an argument of 0, it kills from the start of the +line to dot. +If called with a positive argument, it kills from dot +forward over that number of newlines. +If called with a negative argument +it kills any text before dot on the current line, then it kills back +abs(n) lines. +.It kill-paragraph +Delete +.Va n +paragraphs starting with the current one. +.It kill-region +Kill the currently defined region. +.It kill-word +Delete forward +.Va n +words. +.It leave-tmpdir-backups +Modifies the behaviour of backup-to-home-directory. +Backup files that would normally reside in the system +.Ev TMPDIR +are left there and not moved to the +.Pa ~/.mg.d +directory. +.It line-number-mode +Toggle whether line and column numbers are displayed in the modeline. +.It list-buffers +Display the list of available buffers. +.It load +Prompt the user for a filename, and then execute commands +from that file. +.It local-set-key +Bind a key mapping in the local (topmost) mode. +.It local-unset-key +Unbind a key mapping in the local (topmost) mode. +.It make-backup-files +Toggle generation of backup files. +.It mark-whole-buffer +Marks whole buffer as a region by putting dot at the beginning and mark +at the end of buffer. +.It meta-key-mode +When disabled, the meta key can be used to insert extended-ascii (8-bit) +characters. +When enabled, the meta key acts as usual. +.It negative-argument +Process a negative argument for keyboard-invoked functions. +.It newline +Insert a newline into the current buffer. +.It newline-and-indent +Insert a newline, then enough tabs and spaces to duplicate the indentation +of the previous line. +Assumes tabs are every eight characters. +.It next-line +Move forward +.Va n +lines. +.It no-tab-mode +Toggle notab mode. +In this mode, spaces are inserted rather than tabs. +.It not-modified +Turn off the modified flag in the current buffer. +.It open-line +Open up some blank space. +Essentially, insert +.Va n +newlines, then back up over them. +.It other-window +The command to make the next (down the screen) window the current +window. +There are no real errors, although the command does nothing if +there is only 1 window on the screen. +.It overwrite-mode +Toggle overwrite mode, where typing in a buffer overwrites +existing characters rather than inserting them. +.It prefix-region +Inserts a prefix string before each line of a region. +The prefix string is settable by using 'set-prefix-string'. +.It previous-line +Move backwards +.Va n +lines. +.It previous-window +This command makes the previous (up the screen) window the +current window. +There are no errors, although the command does not do +a lot if there is only 1 window. +.It pop-tag-mark +Return to position where find-tag was previously invoked. +.It push-shell +Suspend +.Nm +and switch to alternate screen, if available. +.It pwd +Display current (global) working directory in the status area. +.It query-replace +Query Replace. +Search and replace strings selectively, prompting after each match. +.It replace-string +Replace string globally without individual prompting. +.It query-replace-regexp +Replace strings selectively. +Does a search and replace operation using regular +expressions for both patterns. +.It quoted-insert +Insert the next character verbatim into the current buffer; i.e. ignore +any function bound to that key. +.It re-search-again +Perform a regular expression search again, using the same search +string and direction as the last search command. +.It re-search-backward +Search backwards using a regular expression. +Get a search string from the user, and search, starting at dot +and proceeding toward the front of the buffer. +If found, dot is left +pointing at the first character of the pattern [the last character that +was matched]. +.It re-search-forward +Search forward using a regular expression. +Get a search string from the user and search for it starting at dot. +If found, move dot to just after the matched characters. +display does all +the hard stuff. +If not found, it just prints a message. +.It recenter +Reposition dot in the current window. +By default, the dot is centered. +If given a positive argument (n), the display is repositioned to line +n. +If +.Va n +is negative, it is that line from the bottom. +.It redraw-display +Refresh the display. +Recomputes all window sizes in case something has changed. +.It save-buffer +Save the contents of the current buffer if it has been changed, +optionally creating a backup copy. +.It save-buffers-kill-emacs +Offer to save modified buffers and quit +.Nm . +.It save-some-buffers +Look through the list of buffers, offering to save any buffer that +has been changed. +Buffers that are not associated with files (such +as *scratch*, *grep*, *compile*) are ignored. +.It scroll-down +Scroll backwards +.Va n +pages. +A two-line overlap between pages is +assumed. +If given a repeat argument, scrolls back lines, not pages. +.It scroll-one-line-down +Scroll the display down +.Va n +lines without changing the cursor position. +.It scroll-one-line-up +Scroll the display +.Va n +lines up without moving the cursor position. +.It scroll-other-window +Scroll the next window in the window list window forward +.Va n +pages. +.It scroll-up +Scroll forward one page. +A two-line overlap between pages is +assumed. +If given a repeat argument, scrolls back lines, not pages. +.It search-again +Search again, using the same search string and direction as the last +search command. +.It search-backward +Reverse search. +Get a search string from the user, and search, starting +at dot and proceeding toward the front of the buffer. +If found, dot is +left pointing at the first character of the pattern (the last character +that was matched). +.It search-forward +Search forward. +Get a search string from the user, and search for it +starting at dot. +If found, dot gets moved to just after the matched +characters, if not found, print a message. +.It self-insert-command +Insert a character. +.It set-case-fold-search +Set case-fold searching, causing case not to matter +in regular expression searches. +This is the default. +.It set-default-mode +Append the supplied mode to the list of default modes +used by subsequent buffer creation. +Built in modes include: fill, indent, overwrite, and notab. +.It set-fill-column +Prompt the user for a fill column. +Used by auto-fill-mode. +.It set-mark-command +Sets the mark in the current window to the current dot location. +.It set-prefix-string +Sets the prefix string to be used by the 'prefix-region' command. +.It shell-command-on-region +Provide the text in region to the shell command as input. +.It shrink-window +Shrink current window by one line. +The window immediately below is expanded to pick up the slack. +If only one window is present, this command has no effect. +.It space-to-tabstop +Insert enough spaces to reach the next tab-stop position. +By default, tab-stops occur every 8 characters. +.It split-window-vertically +Split the current window. +A window smaller than 3 lines cannot be split. +.It start-kbd-macro +Start defining a keyboard macro. +Macro definition is ended by invoking end-kbd-macro. +.It suspend-emacs +Suspend +.Nm +and switch back to alternate screen, if in use. +.It switch-to-buffer +Prompt and switch to a new buffer in the current window. +.It switch-to-buffer-other-window +Switch to buffer in another window. +.It toggle-read-only +Toggle the read-only flag on the current buffer. +.It transpose-chars +Transpose the two characters on either side of dot. +If dot is at the end of the line, transpose the two characters before it. +Return with an error if dot is at the beginning of line; +it seems to be a bit pointless to +make this work. +.It undo +Undo the most recent action. +If invoked again without an intervening command, +move the undo pointer to the previous action and undo it. +.It undo-boundary +Add an undo boundary. +This is not usually done interactively. +.It undo-boundary-toggle +Toggle whether undo boundaries are generated. +Undo boundaries are often disabled before operations that should +be considered atomically undoable. +.It undo-enable +Toggle whether undo information is kept. +.It undo-list +Show the undo records for the current buffer in a new buffer. +.It universal-argument +Repeat the next command 4 times. +Usually bound to C-u. +This command may be stacked; e.g.\& +C-u C-u C-f moves the cursor forward 16 characters. +.It upcase-region +Upper case region. +Change all of the lower case characters in the region to +upper case. +.It upcase-word +Move the cursor forward by the specified number of words. +As it moves, convert any characters to upper case. +.It visit-tags-table +Record name of the tags file to be used for subsequent find-tag. +.It what-cursor-position +Display a bunch of useful information about the current location of +dot. +The character under the cursor (in octal), the current line, row, +and column, and approximate position of the cursor in the file (as a +percentage) is displayed. +The column position assumes an infinite +position display; it does not truncate just because the screen does. +.It write-file +Ask for a file name and write the contents of the current buffer to +that file. +Update the remembered file name and clear the buffer +changed flag. +.It yank +Yank text from kill-buffer. +Unlike emacs, the +.Nm +kill buffer consists only +of the most recent kill. +It is not a ring. +.El +.Sh CONFIGURATION FILES +There are two configuration files, +.Pa .mg +and +.Pa .mg-TERM . +Here, +.Ev TERM +represents the name of the terminal type; e.g. if the terminal type +is set to +.Dq vt100 , +.Nm +will use +.Pa .mg-vt100 +as a startup file. +The terminal type startup file is used first. +.Pp +The startup file format is a list of commands, one per line, as used for +interactive evaluation. +Strings that are normally entered by the user at any subsequent prompts +may be specified after the command name; e.g.: +.Bd -literal -offset indent +global-set-key ")" self-insert-command +global-set-key "\e^x\e^f" find-file +global-set-key "\ee[Z" backward-char +set-default-mode fill +set-fill-column 72 +auto-execute *.c c-mode +.Ed +.Sh FILES +.Bl -tag -width /usr/share/doc/mg/tutorial -compact +.It Pa ~/.mg +normal startup file +.It Pa ~/.mg-TERM +terminal-specific startup file +.It Pa ~/.mg.d +alternative backup file location +.It Pa /usr/share/doc/mg/tutorial +concise tutorial +.El +.Sh SEE ALSO +.Xr ctags 1 , +.Xr vi 1 +.Sh CAVEATS +Since it is written completely in C, there is currently no +language in which extensions can be written; +however, keys can be rebound and certain parameters can be changed +in startup files. +.Pp +In order to use 8-bit characters (such as German umlauts), the Meta key +needs to be disabled via the +.Dq meta-key-mode +command. + diff --git a/mg/modes.c b/mg/modes.c new file mode 100644 index 0000000..cde8728 --- /dev/null +++ b/mg/modes.c @@ -0,0 +1,165 @@ +/* $OpenBSD: modes.c,v 1.18 2008/06/14 08:46:30 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * Commands to toggle modes. Without an argument, these functions will + * toggle the given mode. A negative or zero argument will turn the mode + * off. A positive argument will turn the mode on. + */ + +#include "def.h" +#include "kbd.h" + +int changemode(int, int, char *); + +int defb_nmodes = 0; +struct maps_s *defb_modes[PBMODES] = { &fundamental_mode }; +int defb_flag = 0; + +int +changemode(int f, int n, char *mode) +{ + int i; + struct maps_s *m; + + if ((m = name_mode(mode)) == NULL) { + ewprintf("Can't find mode %s", mode); + return (FALSE); + } + if (!(f & FFARG)) { + for (i = 0; i <= curbp->b_nmodes; i++) + if (curbp->b_modes[i] == m) { + /* mode already set */ + n = 0; + break; + } + } + if (n > 0) { + for (i = 0; i <= curbp->b_nmodes; i++) + if (curbp->b_modes[i] == m) + /* mode already set */ + return (TRUE); + if (curbp->b_nmodes >= PBMODES - 1) { + ewprintf("Too many modes"); + return (FALSE); + } + curbp->b_modes[++(curbp->b_nmodes)] = m; + } else { + /* fundamental is b_modes[0] and can't be unset */ + for (i = 1; i <= curbp->b_nmodes && m != curbp->b_modes[i]; + i++); + if (i > curbp->b_nmodes) + return (TRUE); /* mode wasn't set */ + for (; i < curbp->b_nmodes; i++) + curbp->b_modes[i] = curbp->b_modes[i + 1]; + curbp->b_nmodes--; + } + upmodes(curbp); + return (TRUE); +} + +int +indentmode(int f, int n) +{ + return (changemode(f, n, "indent")); +} + +int +fillmode(int f, int n) +{ + return (changemode(f, n, "fill")); +} + +#ifdef NOTAB +int +notabmode(int f, int n) +{ + if (changemode(f, n, "notab") == FALSE) + return (FALSE); + if (f & FFARG) { + if (n <= 0) + curbp->b_flag &= ~BFNOTAB; + else + curbp->b_flag |= BFNOTAB; + } else + curbp->b_flag ^= BFNOTAB; + return (TRUE); +} +#endif /* NOTAB */ + +int +overwrite_mode(int f, int n) +{ + if (changemode(f, n, "overwrite") == FALSE) + return (FALSE); + if (f & FFARG) { + if (n <= 0) + curbp->b_flag &= ~BFOVERWRITE; + else + curbp->b_flag |= BFOVERWRITE; + } else + curbp->b_flag ^= BFOVERWRITE; + return (TRUE); +} + +int +set_default_mode(int f, int n) +{ + int i; + struct maps_s *m; + char modebuf[32], *bufp; + + if ((bufp = eread("Set Default Mode: ", modebuf, sizeof(modebuf), + EFNEW)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + if ((m = name_mode(modebuf)) == NULL) { + ewprintf("can't find mode %s", modebuf); + return (FALSE); + } + if (!(f & FFARG)) { + for (i = 0; i <= defb_nmodes; i++) + if (defb_modes[i] == m) { + /* mode already set */ + n = 0; + break; + } + } + if (n > 0) { + for (i = 0; i <= defb_nmodes; i++) + if (defb_modes[i] == m) + /* mode already set */ + return (TRUE); + if (defb_nmodes >= PBMODES - 1) { + ewprintf("Too many modes"); + return (FALSE); + } + defb_modes[++defb_nmodes] = m; + } else { + /* fundamental is defb_modes[0] and can't be unset */ + for (i = 1; i <= defb_nmodes && m != defb_modes[i]; i++); + if (i > defb_nmodes) + /* mode was not set */ + return (TRUE); + for (; i < defb_nmodes; i++) + defb_modes[i] = defb_modes[i + 1]; + defb_nmodes--; + } + if (strcmp(modebuf, "overwrite") == 0) { + if (n <= 0) + defb_flag &= ~BFOVERWRITE; + else + defb_flag |= BFOVERWRITE; + } +#ifdef NOTAB + if (strcmp(modebuf, "notab") == 0) { + if (n <= 0) + defb_flag &= ~BFNOTAB; + else + defb_flag |= BFNOTAB; + } +#endif /* NOTAB */ + return (TRUE); +} diff --git a/mg/paragraph.c b/mg/paragraph.c new file mode 100644 index 0000000..57ce412 --- /dev/null +++ b/mg/paragraph.c @@ -0,0 +1,362 @@ +/* $OpenBSD: paragraph.c,v 1.22 2011/11/29 05:59:54 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Code for dealing with paragraphs and filling. Adapted from MicroEMACS 3.6 + * and GNU-ified by mwm@ucbvax. Several bug fixes by blarson@usc-oberon. + */ + +#include "def.h" + +static int fillcol = 70; + +#define MAXWORD 256 + +/* + * Move to start of paragraph. Go back to the beginning of the current + * paragraph here we look for a or or + * combination to delimit the beginning of a paragraph. + */ +/* ARGSUSED */ +int +gotobop(int f, int n) +{ + /* the other way... */ + if (n < 0) + return (gotoeop(f, -n)); + + while (n-- > 0) { + /* first scan back until we are in a word */ + while (backchar(FFRAND, 1) && inword() == 0); + + /* and go to the B-O-Line */ + curwp->w_doto = 0; + + /* + * and scan back until we hit a or + * + */ + while (lback(curwp->w_dotp) != curbp->b_headp) + if (llength(lback(curwp->w_dotp)) && + lgetc(curwp->w_dotp, 0) != ' ' && + lgetc(curwp->w_dotp, 0) != '.' && + lgetc(curwp->w_dotp, 0) != '\t') + curwp->w_dotp = lback(curwp->w_dotp); + else { + if (llength(lback(curwp->w_dotp)) && + lgetc(curwp->w_dotp, 0) == '.') { + curwp->w_dotp = lforw(curwp->w_dotp); + if (curwp->w_dotp == curbp->b_headp) { + /* + * beyond end of buffer, + * cleanup time + */ + curwp->w_dotp = + lback(curwp->w_dotp); + curwp->w_doto = + llength(curwp->w_dotp); + } + } + break; + } + } + /* force screen update */ + curwp->w_rflag |= WFMOVE; + return (TRUE); +} + +/* + * Move to end of paragraph. Go forward to the end of the current paragraph + * here we look for a or or combination to + * delimit the beginning of a paragraph. + */ +/* ARGSUSED */ +int +gotoeop(int f, int n) +{ + /* the other way... */ + if (n < 0) + return (gotobop(f, -n)); + + /* for each one asked for */ + while (n-- > 0) { + /* Find the first word on/after the current line */ + curwp->w_doto = 0; + while (forwchar(FFRAND, 1) && inword() == 0); + + curwp->w_doto = 0; + curwp->w_dotp = lforw(curwp->w_dotp); + + /* and scan forword until we hit a or ... */ + while (curwp->w_dotp != curbp->b_headp) { + if (llength(curwp->w_dotp) && + lgetc(curwp->w_dotp, 0) != ' ' && + lgetc(curwp->w_dotp, 0) != '.' && + lgetc(curwp->w_dotp, 0) != '\t') + curwp->w_dotp = lforw(curwp->w_dotp); + else + break; + } + if (curwp->w_dotp == curbp->b_headp) { + /* beyond end of buffer, cleanup time */ + curwp->w_dotp = lback(curwp->w_dotp); + curwp->w_doto = llength(curwp->w_dotp); + break; + } + } + /* force screen update */ + curwp->w_rflag |= WFMOVE; + return (TRUE); +} + +/* + * Justify a paragraph. Fill the current paragraph according to the current + * fill column. + */ +/* ARGSUSED */ +int +fillpara(int f, int n) +{ + int c; /* current char during scan */ + int wordlen; /* length of current word */ + int clength; /* position on line during fill */ + int i; /* index during word copy */ + int eopflag; /* Are we at the End-Of-Paragraph? */ + int firstflag; /* first word? (needs no space) */ + int newlength; /* tentative new line length */ + int eolflag; /* was at end of line */ + int retval; /* return value */ + struct line *eopline; /* pointer to line just past EOP */ + char wbuf[MAXWORD]; /* buffer for current word */ + + undo_boundary_enable(FFRAND, 0); + + /* record the pointer to the line just past the EOP */ + (void)gotoeop(FFRAND, 1); + if (curwp->w_doto != 0) { + /* paragraph ends at end of buffer */ + (void)lnewline(); + eopline = lforw(curwp->w_dotp); + } else + eopline = curwp->w_dotp; + + /* and back top the begining of the paragraph */ + (void)gotobop(FFRAND, 1); + + /* initialize various info */ + while (inword() == 0 && forwchar(FFRAND, 1)); + + clength = curwp->w_doto; + wordlen = 0; + + /* scan through lines, filling words */ + firstflag = TRUE; + eopflag = FALSE; + while (!eopflag) { + + /* get the next character in the paragraph */ + if ((eolflag = (curwp->w_doto == llength(curwp->w_dotp)))) { + c = ' '; + if (lforw(curwp->w_dotp) == eopline) + eopflag = TRUE; + } else + c = lgetc(curwp->w_dotp, curwp->w_doto); + + /* and then delete it */ + if (ldelete((RSIZE) 1, KNONE) == FALSE && !eopflag) { + retval = FALSE; + goto cleanup; + } + + /* if not a separator, just add it in */ + if (c != ' ' && c != '\t') { + if (wordlen < MAXWORD - 1) + wbuf[wordlen++] = c; + else { + /* + * You lose chars beyond MAXWORD if the word + * is too long. I'm too lazy to fix it now; it + * just silently truncated the word before, + * so I get to feel smug. + */ + ewprintf("Word too long!"); + } + } else if (wordlen) { + + /* calculate tentative new length with word added */ + newlength = clength + 1 + wordlen; + + /* + * if at end of line or at doublespace and previous + * character was one of '.','?','!' doublespace here. + * behave the same way if a ')' is preceded by a + * [.?!] and followed by a doublespace. + */ + if ((eolflag || + curwp->w_doto == llength(curwp->w_dotp) || + (c = lgetc(curwp->w_dotp, curwp->w_doto)) == ' ' + || c == '\t') && (ISEOSP(wbuf[wordlen - 1]) || + (wbuf[wordlen - 1] == ')' && wordlen >= 2 && + ISEOSP(wbuf[wordlen - 2]))) && + wordlen < MAXWORD - 1) + wbuf[wordlen++] = ' '; + + /* at a word break with a word waiting */ + if (newlength <= fillcol) { + /* add word to current line */ + if (!firstflag) { + (void)linsert(1, ' '); + ++clength; + } + firstflag = FALSE; + } else { + if (curwp->w_doto > 0 && + lgetc(curwp->w_dotp, curwp->w_doto - 1) == ' ') { + curwp->w_doto -= 1; + (void)ldelete((RSIZE) 1, KNONE); + } + /* start a new line */ + (void)lnewline(); + clength = 0; + } + + /* and add the word in in either case */ + for (i = 0; i < wordlen; i++) { + (void)linsert(1, wbuf[i]); + ++clength; + } + wordlen = 0; + } + } + /* and add a last newline for the end of our new paragraph */ + (void)lnewline(); + + /* + * We really should wind up where we started, (which is hard to keep + * track of) but I think the end of the last line is better than the + * beginning of the blank line. + */ + (void)backchar(FFRAND, 1); + retval = TRUE; +cleanup: + undo_boundary_enable(FFRAND, 1); + return (retval); +} + +/* + * Delete a paragraph. Delete n paragraphs starting with the current one. + */ +/* ARGSUSED */ +int +killpara(int f, int n) +{ + int status; /* returned status of functions */ + + /* for each paragraph to delete */ + while (n--) { + + /* mark out the end and beginning of the para to delete */ + (void)gotoeop(FFRAND, 1); + + /* set the mark here */ + curwp->w_markp = curwp->w_dotp; + curwp->w_marko = curwp->w_doto; + + /* go to the beginning of the paragraph */ + (void)gotobop(FFRAND, 1); + + /* force us to the beginning of line */ + curwp->w_doto = 0; + + /* and delete it */ + if ((status = killregion(FFRAND, 1)) != TRUE) + return (status); + + /* and clean up the 2 extra lines */ + (void)ldelete((RSIZE) 1, KFORW); + } + return (TRUE); +} + +/* + * Insert char with work wrap. Check to see if we're past fillcol, and if so, + * justify this line. As a last step, justify the line. + */ +/* ARGSUSED */ +int +fillword(int f, int n) +{ + char c; + int col, i, nce; + + for (i = col = 0; col <= fillcol; ++i, ++col) { + if (i == curwp->w_doto) + return selfinsert(f, n); + c = lgetc(curwp->w_dotp, i); + if (c == '\t' +#ifdef NOTAB + && !(curbp->b_flag & BFNOTAB) +#endif + ) + col |= 0x07; + else if (ISCTRL(c) != FALSE) + ++col; + } + if (curwp->w_doto != llength(curwp->w_dotp)) { + (void)selfinsert(f, n); + nce = llength(curwp->w_dotp) - curwp->w_doto; + } else + nce = 0; + curwp->w_doto = i; + + if ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && c != '\t') + do { + (void)backchar(FFRAND, 1); + } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && + c != '\t' && curwp->w_doto > 0); + + if (curwp->w_doto == 0) + do { + (void)forwchar(FFRAND, 1); + } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && + c != '\t' && curwp->w_doto < llength(curwp->w_dotp)); + + (void)delwhite(FFRAND, 1); + (void)lnewline(); + i = llength(curwp->w_dotp) - nce; + curwp->w_doto = i > 0 ? i : 0; + curwp->w_rflag |= WFMOVE; + if (nce == 0 && curwp->w_doto != 0) + return (fillword(f, n)); + return (TRUE); +} + +/* + * Set fill column to n for justify. + */ +int +setfillcol(int f, int n) +{ + char buf[32], *rep; + const char *es; + int nfill; + + if ((f & FFARG) != 0) { + fillcol = n; + } else { + if ((rep = eread("Set fill-column: ", buf, sizeof(buf), + EFNEW | EFCR)) == NULL) + return (ABORT); + else if (rep[0] == '\0') + return (FALSE); + nfill = strtonum(rep, 0, INT_MAX, &es); + if (es != NULL) { + ewprintf("Invalid fill column: %s", rep); + return (FALSE); + } + fillcol = nfill; + ewprintf("Fill column set to %d", fillcol); + } + return (TRUE); +} diff --git a/mg/pathnames.h b/mg/pathnames.h new file mode 100644 index 0000000..9720076 --- /dev/null +++ b/mg/pathnames.h @@ -0,0 +1,11 @@ +/* $OpenBSD: pathnames.h,v 1.1 2012/06/18 07:14:55 jasper Exp $ */ + +/* This file is in the public domain. */ + +/* + * standard path names + */ + +#define _PATH_MG_DIR "~/.mg.d" +#define _PATH_MG_STARTUP "%s/.mg" +#define _PATH_MG_TERM "%s/.mg-%s" diff --git a/mg/random.c b/mg/random.c new file mode 100644 index 0000000..27fd0c3 --- /dev/null +++ b/mg/random.c @@ -0,0 +1,496 @@ +/* $OpenBSD: random.c,v 1.31 2012/05/18 02:13:44 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Assorted commands. + * This file contains the command processors for a large assortment of + * unrelated commands. The only thing they have in common is that they + * are all command processors. + */ + +#include "def.h" + +#include + +/* + * Display a bunch of useful information about the current location of dot. + * The character under the cursor (in octal), the current line, row, and + * column, and approximate position of the cursor in the file (as a + * percentage) is displayed. The column position assumes an infinite + * position display; it does not truncate just because the screen does. + * This is normally bound to "C-X =". + */ +/* ARGSUSED */ +int +showcpos(int f, int n) +{ + struct line *clp; + long nchar, cchar; + int nline, row; + int cline, cbyte; /* Current line/char/byte */ + int ratio; + + /* collect the data */ + clp = bfirstlp(curbp); + cchar = 0; + cline = 0; + cbyte = 0; + nchar = 0; + nline = 0; + for (;;) { + /* count this line */ + ++nline; + if (clp == curwp->w_dotp) { + /* mark line */ + cline = nline; + cchar = nchar + curwp->w_doto; + if (curwp->w_doto == llength(clp)) + cbyte = '\n'; + else + cbyte = lgetc(clp, curwp->w_doto); + } + /* now count the chars */ + nchar += llength(clp); + clp = lforw(clp); + if (clp == curbp->b_headp) + break; + /* count the newline */ + nchar++; + } + /* determine row */ + row = curwp->w_toprow + 1; + clp = curwp->w_linep; + while (clp != curbp->b_headp && clp != curwp->w_dotp) { + ++row; + clp = lforw(clp); + } + /* NOSTRICT */ + ratio = nchar ? (100L * cchar) / nchar : 100; + ewprintf("Char: %c (0%o) point=%ld(%d%%) line=%d row=%d col=%d", + cbyte, cbyte, cchar, ratio, cline, row, getcolpos()); + return (TRUE); +} + +int +getcolpos(void) +{ + int col, i, c; + char tmp[5]; + + /* determine column */ + col = 0; + + for (i = 0; i < curwp->w_doto; ++i) { + c = lgetc(curwp->w_dotp, i); + if (c == '\t' +#ifdef NOTAB + && !(curbp->b_flag & BFNOTAB) +#endif /* NOTAB */ + ) { + col |= 0x07; + col++; + } else if (ISCTRL(c) != FALSE) + col += 2; + else if (isprint(c)) { + col++; + } else { + col += snprintf(tmp, sizeof(tmp), "\\%o", c); + } + + } + return (col); +} + +/* + * Twiddle the two characters on either side of dot. If dot is at the end + * of the line twiddle the two characters before it. Return with an error + * if dot is at the beginning of line; it seems to be a bit pointless to + * make this work. This fixes up a very common typo with a single stroke. + * Normally bound to "C-T". This always works within a line, so "WFEDIT" + * is good enough. + */ +/* ARGSUSED */ +int +twiddle(int f, int n) +{ + struct line *dotp; + int doto, cr; + int fudge = FALSE; + + dotp = curwp->w_dotp; + doto = curwp->w_doto; + if (doto == llength(dotp)) { + if (--doto <= 0) + return (FALSE); + (void)backchar(FFRAND, 1); + fudge = TRUE; + } else { + if (doto == 0) + return (FALSE); + } + undo_boundary_enable(FFRAND, 0); + cr = lgetc(dotp, doto - 1); + (void)backdel(FFRAND, 1); + (void)forwchar(FFRAND, 1); + linsert(1, cr); + if (fudge != TRUE) + (void)backchar(FFRAND, 1); + undo_boundary_enable(FFRAND, 1); + lchange(WFEDIT); + return (TRUE); +} + +/* + * Open up some blank space. The basic plan is to insert a bunch of + * newlines, and then back up over them. Everything is done by the + * subcommand processors. They even handle the looping. Normally this + * is bound to "C-O". + */ +/* ARGSUSED */ +int +openline(int f, int n) +{ + int i, s; + + if (n < 0) + return (FALSE); + if (n == 0) + return (TRUE); + + /* insert newlines */ + undo_boundary_enable(FFRAND, 0); + i = n; + do { + s = lnewline(); + } while (s == TRUE && --i); + + /* then go back up overtop of them all */ + if (s == TRUE) + s = backchar(f | FFRAND, n); + undo_boundary_enable(FFRAND, 1); + return (s); +} + +/* + * Insert a newline. + */ +/* ARGSUSED */ +int +newline(int f, int n) +{ + int s; + + if (n < 0) + return (FALSE); + + while (n--) { + if ((s = lnewline()) != TRUE) + return (s); + } + return (TRUE); +} + +/* + * Delete blank lines around dot. What this command does depends if dot is + * sitting on a blank line. If dot is sitting on a blank line, this command + * deletes all the blank lines above and below the current line. If it is + * sitting on a non blank line then it deletes all of the blank lines after + * the line. Normally this command is bound to "C-X C-O". Any argument is + * ignored. + */ +/* ARGSUSED */ +int +deblank(int f, int n) +{ + struct line *lp1, *lp2; + RSIZE nld; + + lp1 = curwp->w_dotp; + while (llength(lp1) == 0 && (lp2 = lback(lp1)) != curbp->b_headp) + lp1 = lp2; + lp2 = lp1; + nld = (RSIZE)0; + while ((lp2 = lforw(lp2)) != curbp->b_headp && llength(lp2) == 0) + ++nld; + if (nld == 0) + return (TRUE); + curwp->w_dotp = lforw(lp1); + curwp->w_doto = 0; + return (ldelete((RSIZE)nld, KNONE)); +} + +/* + * Delete any whitespace around dot, then insert a space. + */ +int +justone(int f, int n) +{ + undo_boundary_enable(FFRAND, 0); + (void)delwhite(f, n); + linsert(1, ' '); + undo_boundary_enable(FFRAND, 1); + return (TRUE); +} + +/* + * Delete any whitespace around dot. + */ +/* ARGSUSED */ +int +delwhite(int f, int n) +{ + int col, s; + + col = curwp->w_doto; + + while (col < llength(curwp->w_dotp) && + (isspace(lgetc(curwp->w_dotp, col)))) + ++col; + do { + if (curwp->w_doto == 0) { + s = FALSE; + break; + } + if ((s = backchar(FFRAND, 1)) != TRUE) + break; + } while (isspace(lgetc(curwp->w_dotp, curwp->w_doto))); + + if (s == TRUE) + (void)forwchar(FFRAND, 1); + (void)ldelete((RSIZE)(col - curwp->w_doto), KNONE); + return (TRUE); +} + +/* + * Delete any leading whitespace on the current line + */ +int +delleadwhite(int f, int n) +{ + int soff, ls; + struct line *slp; + + /* Save current position */ + slp = curwp->w_dotp; + soff = curwp->w_doto; + + for (ls = 0; ls < llength(slp); ls++) + if (!isspace(lgetc(slp, ls))) + break; + gotobol(FFRAND, 1); + forwdel(FFRAND, ls); + soff -= ls; + if (soff < 0) + soff = 0; + forwchar(FFRAND, soff); + + return (TRUE); +} + +/* + * Delete any trailing whitespace on the current line + */ +int +deltrailwhite(int f, int n) +{ + int soff; + + /* Save current position */ + soff = curwp->w_doto; + + gotoeol(FFRAND, 1); + delwhite(FFRAND, 1); + + /* restore original position, if possible */ + if (soff < curwp->w_doto) + curwp->w_doto = soff; + + return (TRUE); +} + + + +/* + * Insert a newline, then enough tabs and spaces to duplicate the indentation + * of the previous line. Assumes tabs are every eight characters. Quite + * simple. Figure out the indentation of the current line. Insert a newline + * by calling the standard routine. Insert the indentation by inserting the + * right number of tabs and spaces. Return TRUE if all ok. Return FALSE if + * one of the subcommands failed. Normally bound to "C-M". + */ +/* ARGSUSED */ +int +lfindent(int f, int n) +{ + int c, i, nicol; + int s = TRUE; + + if (n < 0) + return (FALSE); + + undo_boundary_enable(FFRAND, 0); + while (n--) { + nicol = 0; + for (i = 0; i < llength(curwp->w_dotp); ++i) { + c = lgetc(curwp->w_dotp, i); + if (c != ' ' && c != '\t') + break; + if (c == '\t') + nicol |= 0x07; + ++nicol; + } + if (lnewline() == FALSE || (( +#ifdef NOTAB + curbp->b_flag & BFNOTAB) ? linsert(nicol, ' ') == FALSE : ( +#endif /* NOTAB */ + ((i = nicol / 8) != 0 && linsert(i, '\t') == FALSE) || + ((i = nicol % 8) != 0 && linsert(i, ' ') == FALSE)))) { + s = FALSE; + break; + } + } + undo_boundary_enable(FFRAND, 1); + return (s); +} + +/* + * Indent the current line. Delete existing leading whitespace, + * and use tabs/spaces to achieve correct indentation. Try + * to leave dot where it started. + */ +int +indent(int f, int n) +{ + int soff, i; + + if (n < 0) + return (FALSE); + + delleadwhite(FFRAND, 1); + + /* If not invoked with a numerical argument, done */ + if (!(f & FFARG)) + return (TRUE); + + /* insert appropriate whitespace */ + soff = curwp->w_doto; + (void)gotobol(FFRAND, 1); + if ( +#ifdef NOTAB + (curbp->b_flag & BFNOTAB) ? linsert(n, ' ') == FALSE : +#endif /* NOTAB */ + (((i = n / 8) != 0 && linsert(i, '\t') == FALSE) || + ((i = n % 8) != 0 && linsert(i, ' ') == FALSE))) + return (FALSE); + + forwchar(FFRAND, soff); + + return (TRUE); +} + + +/* + * Delete forward. This is real easy, because the basic delete routine does + * all of the work. Watches for negative arguments, and does the right thing. + * If any argument is present, it kills rather than deletes, to prevent loss + * of text if typed with a big argument. Normally bound to "C-D". + */ +/* ARGSUSED */ +int +forwdel(int f, int n) +{ + if (n < 0) + return (backdel(f | FFRAND, -n)); + + /* really a kill */ + if (f & FFARG) { + if ((lastflag & CFKILL) == 0) + kdelete(); + thisflag |= CFKILL; + } + + return (ldelete((RSIZE) n, (f & FFARG) ? KFORW : KNONE)); +} + +/* + * Delete backwards. This is quite easy too, because it's all done with + * other functions. Just move the cursor back, and delete forwards. Like + * delete forward, this actually does a kill if presented with an argument. + */ +/* ARGSUSED */ +int +backdel(int f, int n) +{ + int s; + + if (n < 0) + return (forwdel(f | FFRAND, -n)); + + /* really a kill */ + if (f & FFARG) { + if ((lastflag & CFKILL) == 0) + kdelete(); + thisflag |= CFKILL; + } + if ((s = backchar(f | FFRAND, n)) == TRUE) + s = ldelete((RSIZE)n, (f & FFARG) ? KFORW : KNONE); + + return (s); +} + +#ifdef NOTAB +/* ARGSUSED */ +int +space_to_tabstop(int f, int n) +{ + if (n < 0) + return (FALSE); + if (n == 0) + return (TRUE); + return (linsert((n << 3) - (curwp->w_doto & 7), ' ')); +} +#endif /* NOTAB */ + +/* + * Move the dot to the first non-whitespace character of the current line. + */ +int +backtoindent(int f, int n) +{ + gotobol(FFRAND, 1); + while (curwp->w_doto < llength(curwp->w_dotp) && + (isspace(lgetc(curwp->w_dotp, curwp->w_doto)))) + ++curwp->w_doto; + return (TRUE); +} + +/* + * Join the current line to the previous, or with arg, the next line + * to the current one. If the former line is not empty, leave exactly + * one space at the joint. Otherwise, leave no whitespace. + */ +int +joinline(int f, int n) +{ + int doto; + + undo_boundary_enable(FFRAND, 0); + if (f & FFARG) { + gotoeol(FFRAND, 1); + forwdel(FFRAND, 1); + } else { + gotobol(FFRAND, 1); + backdel(FFRAND, 1); + } + + delwhite(FFRAND, 1); + + if ((doto = curwp->w_doto) > 0) { + linsert(1, ' '); + curwp->w_doto = doto; + } + undo_boundary_enable(FFRAND, 1); + + return (TRUE); +} diff --git a/mg/re_search.c b/mg/re_search.c new file mode 100644 index 0000000..db39fdc --- /dev/null +++ b/mg/re_search.c @@ -0,0 +1,615 @@ +/* $OpenBSD: re_search.c,v 1.26 2011/01/21 19:10:13 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * regular expression search commands for Mg + * + * This file contains functions to implement several of gnuemacs's regular + * expression functions for Mg. Several of the routines below are just minor + * re-arrangements of Mg's non-regular expression search functions. Some of + * them are similar in structure to the original MicroEMACS, others are + * modifications of Rich Ellison's code. Peter Newton re-wrote about half of + * them from scratch. + */ + +#ifdef REGEX +#include "def.h" + +#include +#include + +#include "macro.h" + +#define SRCH_BEGIN (0) /* search sub-codes */ +#define SRCH_FORW (-1) +#define SRCH_BACK (-2) +#define SRCH_NOPR (-3) +#define SRCH_ACCM (-4) +#define SRCH_MARK (-5) + +#define RE_NMATCH 10 /* max number of matches */ +#define REPLEN 256 /* max length of replacement string */ + +char re_pat[NPAT]; /* regex pattern */ +int re_srch_lastdir = SRCH_NOPR; /* last search flags */ +int casefoldsearch = TRUE; /* does search ignore case? */ + +static int re_doreplace(RSIZE, char *); +static int re_forwsrch(void); +static int re_backsrch(void); +static int re_readpattern(char *); +static int killmatches(int); +static int countmatches(int); + +/* + * Search forward. + * Get a search string from the user and search for it starting at ".". If + * found, move "." to just after the matched characters. display does all + * the hard stuff. If not found, it just prints a message. + */ +/* ARGSUSED */ +int +re_forwsearch(int f, int n) +{ + int s; + + if ((s = re_readpattern("RE Search")) != TRUE) + return (s); + if (re_forwsrch() == FALSE) { + ewprintf("Search failed: \"%s\"", re_pat); + return (FALSE); + } + re_srch_lastdir = SRCH_FORW; + return (TRUE); +} + +/* + * Reverse search. + * Get a search string from the user, and search, starting at "." + * and proceeding toward the front of the buffer. If found "." is left + * pointing at the first character of the pattern [the last character that + * was matched]. + */ +/* ARGSUSED */ +int +re_backsearch(int f, int n) +{ + int s; + + if ((s = re_readpattern("RE Search backward")) != TRUE) + return (s); + if (re_backsrch() == FALSE) { + ewprintf("Search failed: \"%s\"", re_pat); + return (FALSE); + } + re_srch_lastdir = SRCH_BACK; + return (TRUE); +} + +/* + * Search again, using the same search string and direction as the last search + * command. The direction has been saved in "srch_lastdir", so you know which + * way to go. + * + * XXX: This code has problems -- some incompatibility(?) with extend.c causes + * match to fail when it should not. + */ +/* ARGSUSED */ +int +re_searchagain(int f, int n) +{ + if (re_srch_lastdir == SRCH_NOPR) { + ewprintf("No last search"); + return (FALSE); + } + if (re_srch_lastdir == SRCH_FORW) { + if (re_forwsrch() == FALSE) { + ewprintf("Search failed: \"%s\"", re_pat); + return (FALSE); + } + return (TRUE); + } + if (re_srch_lastdir == SRCH_BACK) + if (re_backsrch() == FALSE) { + ewprintf("Search failed: \"%s\"", re_pat); + return (FALSE); + } + + return (TRUE); +} + +/* Compiled regex goes here-- changed only when new pattern read */ +static regex_t re_buff; +static regmatch_t re_match[RE_NMATCH]; + +/* + * Re-Query Replace. + * Replace strings selectively. Does a search and replace operation. + */ +/* ARGSUSED */ +int +re_queryrepl(int f, int n) +{ + int rcnt = 0; /* replacements made so far */ + int plen, s; /* length of found string */ + char news[NPAT]; /* replacement string */ + + if ((s = re_readpattern("RE Query replace")) != TRUE) + return (s); + if (eread("Query replace %s with: ", news, NPAT, + EFNUL | EFNEW | EFCR, re_pat) == NULL) + return (ABORT); + ewprintf("Query replacing %s with %s:", re_pat, news); + + /* + * Search forward repeatedly, checking each time whether to insert + * or not. The "!" case makes the check always true, so it gets put + * into a tighter loop for efficiency. + */ + while (re_forwsrch() == TRUE) { +retry: + update(); + switch (getkey(FALSE)) { + case ' ': + plen = re_match[0].rm_eo - re_match[0].rm_so; + if (re_doreplace((RSIZE)plen, news) == FALSE) + return (FALSE); + rcnt++; + break; + + case '.': + plen = re_match[0].rm_eo - re_match[0].rm_so; + if (re_doreplace((RSIZE)plen, news) == FALSE) + return (FALSE); + rcnt++; + goto stopsearch; + + case CCHR('G'): /* ^G */ + (void)ctrlg(FFRAND, 0); + goto stopsearch; + case CCHR('['): /* ESC */ + case '`': + goto stopsearch; + case '!': + do { + plen = re_match[0].rm_eo - re_match[0].rm_so; + if (re_doreplace((RSIZE)plen, news) == FALSE) + return (FALSE); + rcnt++; + } while (re_forwsrch() == TRUE); + goto stopsearch; + + case CCHR('?'): /* To not replace */ + break; + + default: + ewprintf(" replace, [.] rep-end, don't, [!] repl rest quit"); + goto retry; + } + } + +stopsearch: + curwp->w_rflag |= WFFULL; + update(); + if (!inmacro) { + if (rcnt == 0) + ewprintf("(No replacements done)"); + else if (rcnt == 1) + ewprintf("(1 replacement done)"); + else + ewprintf("(%d replacements done)", rcnt); + } + return (TRUE); +} + +/* + * Routine re_doreplace calls lreplace to make replacements needed by + * re_query replace. Its reason for existence is to deal with \1, \2. etc. + * plen: length to remove + * st: replacement string + */ +static int +re_doreplace(RSIZE plen, char *st) +{ + int j, k, s, more, num, state; + struct line *clp; + char repstr[REPLEN]; + + clp = curwp->w_dotp; + more = TRUE; + j = 0; + state = 0; + num = 0; + + /* The following FSA parses the replacement string */ + while (more) { + switch (state) { + case 0: + if (*st == '\\') { + st++; + state = 1; + } else if (*st == '\0') + more = FALSE; + else { + repstr[j] = *st; + j++; + if (j >= REPLEN) + return (FALSE); + st++; + } + break; + case 1: + if (*st >= '0' && *st <= '9') { + num = *st - '0'; + st++; + state = 2; + } else if (*st == '\0') + more = FALSE; + else { + repstr[j] = *st; + j++; + if (j >= REPLEN) + return (FALSE); + st++; + state = 0; + } + break; + case 2: + if (*st >= '0' && *st <= '9') { + num = 10 * num + *st - '0'; + st++; + } else { + if (num >= RE_NMATCH) + return (FALSE); + k = re_match[num].rm_eo - re_match[num].rm_so; + if (j + k >= REPLEN) + return (FALSE); + bcopy(&(clp->l_text[re_match[num].rm_so]), + &repstr[j], k); + j += k; + if (*st == '\0') + more = FALSE; + if (*st == '\\') { + st++; + state = 1; + } else { + repstr[j] = *st; + j++; + if (j >= REPLEN) + return (FALSE); + st++; + state = 0; + } + } + break; + } /* switch (state) */ + } /* while (more) */ + + repstr[j] = '\0'; + s = lreplace(plen, repstr); + return (s); +} + +/* + * This routine does the real work of a forward search. The pattern is + * sitting in the external variable "pat". If found, dot is updated, the + * window system is notified of the change, and TRUE is returned. If the + * string isn't found, FALSE is returned. + */ +static int +re_forwsrch(void) +{ + int tbo, error; + struct line *clp; + + clp = curwp->w_dotp; + tbo = curwp->w_doto; + + if (tbo == clp->l_used) + /* + * Don't start matching past end of line -- must move to + * beginning of next line, unless at end of file. + */ + if (clp != curbp->b_headp) { + clp = lforw(clp); + tbo = 0; + } + /* + * Note this loop does not process the last line, but this editor + * always makes the last line empty so this is good. + */ + while (clp != (curbp->b_headp)) { + re_match[0].rm_so = tbo; + re_match[0].rm_eo = llength(clp); + error = regexec(&re_buff, ltext(clp), RE_NMATCH, re_match, + REG_STARTEND); + if (error != 0) { + clp = lforw(clp); + tbo = 0; + } else { + curwp->w_doto = re_match[0].rm_eo; + curwp->w_dotp = clp; + curwp->w_rflag |= WFMOVE; + return (TRUE); + } + } + return (FALSE); +} + +/* + * This routine does the real work of a backward search. The pattern is sitting + * in the external variable "re_pat". If found, dot is updated, the window + * system is notified of the change, and TRUE is returned. If the string isn't + * found, FALSE is returned. + */ +static int +re_backsrch(void) +{ + struct line *clp; + int tbo; + regmatch_t lastmatch; + + clp = curwp->w_dotp; + tbo = curwp->w_doto; + + /* Start search one position to the left of dot */ + tbo = tbo - 1; + if (tbo < 0) { + /* must move up one line */ + clp = lback(clp); + tbo = llength(clp); + } + + /* + * Note this loop does not process the last line, but this editor + * always makes the last line empty so this is good. + */ + while (clp != (curbp->b_headp)) { + re_match[0].rm_so = 0; + re_match[0].rm_eo = llength(clp); + lastmatch.rm_so = -1; + /* + * Keep searching until we don't match any longer. Assumes a + * non-match does not modify the re_match array. We have to + * do this character-by-character after the first match since + * POSIX regexps don't give you a way to do reverse matches. + */ + while (!regexec(&re_buff, ltext(clp), RE_NMATCH, re_match, + REG_STARTEND) && re_match[0].rm_so < tbo) { + memcpy(&lastmatch, &re_match[0], sizeof(regmatch_t)); + re_match[0].rm_so++; + re_match[0].rm_eo = llength(clp); + } + if (lastmatch.rm_so == -1) { + clp = lback(clp); + tbo = llength(clp); + } else { + memcpy(&re_match[0], &lastmatch, sizeof(regmatch_t)); + curwp->w_doto = re_match[0].rm_so; + curwp->w_dotp = clp; + curwp->w_rflag |= WFMOVE; + return (TRUE); + } + } + return (FALSE); +} + +/* + * Read a pattern. + * Stash it in the external variable "re_pat". The "pat" is + * not updated if the user types in an empty line. If the user typed + * an empty line, and there is no old pattern, it is an error. + * Display the old pattern, in the style of Jeff Lomicka. There is + * some do-it-yourself control expansion. + */ +static int +re_readpattern(char *prompt) +{ + static int dofree = 0; + int flags, error, s; + char tpat[NPAT], *rep; + + if (re_pat[0] == '\0') + rep = eread("%s: ", tpat, NPAT, EFNEW | EFCR, prompt); + else + rep = eread("%s: (default %s) ", tpat, NPAT, + EFNUL | EFNEW | EFCR, prompt, re_pat); + if (rep == NULL) + return (ABORT); + if (rep[0] != '\0') { + /* New pattern given */ + (void)strlcpy(re_pat, tpat, sizeof(re_pat)); + if (casefoldsearch) + flags = REG_EXTENDED | REG_ICASE; + else + flags = REG_EXTENDED; + if (dofree) + regfree(&re_buff); + error = regcomp(&re_buff, re_pat, flags); + if (error != 0) { + char message[256]; + regerror(error, &re_buff, message, sizeof(message)); + ewprintf("Regex Error: %s", message); + re_pat[0] = '\0'; + return (FALSE); + } + dofree = 1; + s = TRUE; + } else if (rep[0] == '\0' && re_pat[0] != '\0') + /* Just using old pattern */ + s = TRUE; + else + s = FALSE; + return (s); +} + +/* + * Cause case to not matter in searches. This is the default. If called + * with argument cause case to matter. + */ +/* ARGSUSED*/ +int +setcasefold(int f, int n) +{ + if (f & FFARG) { + casefoldsearch = FALSE; + ewprintf("Case-fold-search unset"); + } else { + casefoldsearch = TRUE; + ewprintf("Case-fold-search set"); + } + + /* + * Invalidate the regular expression pattern since I'm too lazy to + * recompile it. + */ + re_pat[0] = '\0'; + return (TRUE); +} + +/* + * Delete all lines after dot that contain a string matching regex. + */ +/* ARGSUSED */ +int +delmatchlines(int f, int n) +{ + int s; + + if ((s = re_readpattern("Flush lines (containing match for regexp)")) + != TRUE) + return (s); + + s = killmatches(TRUE); + return (s); +} + +/* + * Delete all lines after dot that don't contain a string matching regex. + */ +/* ARGSUSED */ +int +delnonmatchlines(int f, int n) +{ + int s; + + if ((s = re_readpattern("Keep lines (containing match for regexp)")) + != TRUE) + return (s); + + s = killmatches(FALSE); + return (s); +} + +/* + * This function does the work of deleting matching lines. + */ +static int +killmatches(int cond) +{ + int s, error; + int count = 0; + struct line *clp; + + clp = curwp->w_dotp; + if (curwp->w_doto == llength(clp)) + /* Consider dot on next line */ + clp = lforw(clp); + + while (clp != (curbp->b_headp)) { + /* see if line matches */ + re_match[0].rm_so = 0; + re_match[0].rm_eo = llength(clp); + error = regexec(&re_buff, ltext(clp), RE_NMATCH, re_match, + REG_STARTEND); + + /* Delete line when appropriate */ + if ((cond == FALSE && error) || (cond == TRUE && !error)) { + curwp->w_doto = 0; + curwp->w_dotp = clp; + count++; + s = ldelete(llength(clp) + 1, KNONE); + clp = curwp->w_dotp; + curwp->w_rflag |= WFMOVE; + if (s == FALSE) + return (FALSE); + } else + clp = lforw(clp); + } + + ewprintf("%d line(s) deleted", count); + if (count > 0) + curwp->w_rflag |= WFMOVE; + + return (TRUE); +} + +/* + * Count lines matching regex. + */ +/* ARGSUSED */ +int +cntmatchlines(int f, int n) +{ + int s; + + if ((s = re_readpattern("Count lines (matching regexp)")) != TRUE) + return (s); + s = countmatches(TRUE); + + return (s); +} + +/* + * Count lines that fail to match regex. + */ +/* ARGSUSED */ +int +cntnonmatchlines(int f, int n) +{ + int s; + + if ((s = re_readpattern("Count lines (not matching regexp)")) != TRUE) + return (s); + s = countmatches(FALSE); + + return (s); +} + +/* + * This function does the work of counting matching lines. + */ +int +countmatches(int cond) +{ + int error; + int count = 0; + struct line *clp; + + clp = curwp->w_dotp; + if (curwp->w_doto == llength(clp)) + /* Consider dot on next line */ + clp = lforw(clp); + + while (clp != (curbp->b_headp)) { + /* see if line matches */ + re_match[0].rm_so = 0; + re_match[0].rm_eo = llength(clp); + error = regexec(&re_buff, ltext(clp), RE_NMATCH, re_match, + REG_STARTEND); + + /* Count line when appropriate */ + if ((cond == FALSE && error) || (cond == TRUE && !error)) + count++; + clp = lforw(clp); + } + + if (cond) + ewprintf("Number of lines matching: %d", count); + else + ewprintf("Number of lines not matching: %d", count); + + return (TRUE); +} +#endif /* REGEX */ diff --git a/mg/region.c b/mg/region.c new file mode 100644 index 0000000..baca932 --- /dev/null +++ b/mg/region.c @@ -0,0 +1,597 @@ +/* $OpenBSD: region.c,v 1.30 2012/04/11 17:51:10 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Region based commands. + * The routines in this file deal with the region, that magic space between + * "." and mark. Some functions are commands. Some functions are just for + * internal use. + */ + +#include +#include + +#include +#include +#include +#include + +#include "def.h" + +#define TIMEOUT 10000 + +static char leftover[BUFSIZ]; + +static int getregion(struct region *); +static int iomux(int); +static int pipeio(const char *); +static int preadin(int, struct buffer *); +static void pwriteout(int, char **, int *); +static int setsize(struct region *, RSIZE); + +/* + * Kill the region. Ask "getregion" to figure out the bounds of the region. + * Move "." to the start, and kill the characters. Mark is cleared afterwards. + */ +/* ARGSUSED */ +int +killregion(int f, int n) +{ + int s; + struct region region; + + if ((s = getregion(®ion)) != TRUE) + return (s); + /* This is a kill-type command, so do magic kill buffer stuff. */ + if ((lastflag & CFKILL) == 0) + kdelete(); + thisflag |= CFKILL; + curwp->w_dotp = region.r_linep; + curwp->w_doto = region.r_offset; + curwp->w_dotline = region.r_lineno; + s = ldelete(region.r_size, KFORW | KREG); + clearmark(FFARG, 0); + + return (s); +} + +/* + * Copy all of the characters in the region to the kill buffer, + * clearing the mark afterwards. + * This is a bit like a kill region followed by a yank. + */ +/* ARGSUSED */ +int +copyregion(int f, int n) +{ + struct line *linep; + struct region region; + int loffs; + int s; + + if ((s = getregion(®ion)) != TRUE) + return (s); + + /* kill type command */ + if ((lastflag & CFKILL) == 0) + kdelete(); + thisflag |= CFKILL; + + /* current line */ + linep = region.r_linep; + + /* current offset */ + loffs = region.r_offset; + + while (region.r_size--) { + if (loffs == llength(linep)) { /* End of line. */ + if ((s = kinsert('\n', KFORW)) != TRUE) + return (s); + linep = lforw(linep); + loffs = 0; + } else { /* Middle of line. */ + if ((s = kinsert(lgetc(linep, loffs), KFORW)) != TRUE) + return (s); + ++loffs; + } + } + clearmark(FFARG, 0); + + return (TRUE); +} + +/* + * Lower case region. Zap all of the upper case characters in the region to + * lower case. Use the region code to set the limits. Scan the buffer, doing + * the changes. Call "lchange" to ensure that redisplay is done in all + * buffers. + */ +/* ARGSUSED */ +int +lowerregion(int f, int n) +{ + struct line *linep; + struct region region; + int loffs, c, s; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read-only"); + return (FALSE); + } + + if ((s = getregion(®ion)) != TRUE) + return (s); + + undo_add_change(region.r_linep, region.r_offset, region.r_size); + + lchange(WFFULL); + linep = region.r_linep; + loffs = region.r_offset; + while (region.r_size--) { + if (loffs == llength(linep)) { + linep = lforw(linep); + loffs = 0; + } else { + c = lgetc(linep, loffs); + if (ISUPPER(c) != FALSE) + lputc(linep, loffs, TOLOWER(c)); + ++loffs; + } + } + return (TRUE); +} + +/* + * Upper case region. Zap all of the lower case characters in the region to + * upper case. Use the region code to set the limits. Scan the buffer, + * doing the changes. Call "lchange" to ensure that redisplay is done in all + * buffers. + */ +/* ARGSUSED */ +int +upperregion(int f, int n) +{ + struct line *linep; + struct region region; + int loffs, c, s; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read-only"); + return (FALSE); + } + if ((s = getregion(®ion)) != TRUE) + return (s); + + undo_add_change(region.r_linep, region.r_offset, region.r_size); + + lchange(WFFULL); + linep = region.r_linep; + loffs = region.r_offset; + while (region.r_size--) { + if (loffs == llength(linep)) { + linep = lforw(linep); + loffs = 0; + } else { + c = lgetc(linep, loffs); + if (ISLOWER(c) != FALSE) + lputc(linep, loffs, TOUPPER(c)); + ++loffs; + } + } + return (TRUE); +} + +/* + * This routine figures out the bound of the region in the current window, + * and stores the results into the fields of the REGION structure. Dot and + * mark are usually close together, but I don't know the order, so I scan + * outward from dot, in both directions, looking for mark. The size is kept + * in a long. At the end, after the size is figured out, it is assigned to + * the size field of the region structure. If this assignment loses any bits, + * then we print an error. This is "type independent" overflow checking. All + * of the callers of this routine should be ready to get an ABORT status, + * because I might add a "if regions is big, ask before clobbering" flag. + */ +static int +getregion(struct region *rp) +{ + struct line *flp, *blp; + long fsize, bsize; + + if (curwp->w_markp == NULL) { + ewprintf("No mark set in this window"); + return (FALSE); + } + + /* "r_size" always ok */ + if (curwp->w_dotp == curwp->w_markp) { + rp->r_linep = curwp->w_dotp; + rp->r_lineno = curwp->w_dotline; + if (curwp->w_doto < curwp->w_marko) { + rp->r_offset = curwp->w_doto; + rp->r_size = (RSIZE)(curwp->w_marko - curwp->w_doto); + } else { + rp->r_offset = curwp->w_marko; + rp->r_size = (RSIZE)(curwp->w_doto - curwp->w_marko); + } + return (TRUE); + } + /* get region size */ + flp = blp = curwp->w_dotp; + bsize = curwp->w_doto; + fsize = llength(flp) - curwp->w_doto + 1; + while (lforw(flp) != curbp->b_headp || lback(blp) != curbp->b_headp) { + if (lforw(flp) != curbp->b_headp) { + flp = lforw(flp); + if (flp == curwp->w_markp) { + rp->r_linep = curwp->w_dotp; + rp->r_offset = curwp->w_doto; + rp->r_lineno = curwp->w_dotline; + return (setsize(rp, + (RSIZE)(fsize + curwp->w_marko))); + } + fsize += llength(flp) + 1; + } + if (lback(blp) != curbp->b_headp) { + blp = lback(blp); + bsize += llength(blp) + 1; + if (blp == curwp->w_markp) { + rp->r_linep = blp; + rp->r_offset = curwp->w_marko; + rp->r_lineno = curwp->w_markline; + return (setsize(rp, + (RSIZE)(bsize - curwp->w_marko))); + } + } + } + ewprintf("Bug: lost mark"); + return (FALSE); +} + +/* + * Set size, and check for overflow. + */ +static int +setsize(struct region *rp, RSIZE size) +{ + rp->r_size = size; + if (rp->r_size != size) { + ewprintf("Region is too large"); + return (FALSE); + } + return (TRUE); +} + +#define PREFIXLENGTH 40 +static char prefix_string[PREFIXLENGTH] = {'>', '\0'}; + +/* + * Prefix the region with whatever is in prefix_string. Leaves dot at the + * beginning of the line after the end of the region. If an argument is + * given, prompts for the line prefix string. + */ +/* ARGSUSED */ +int +prefixregion(int f, int n) +{ + struct line *first, *last; + struct region region; + char *prefix = prefix_string; + int nline; + int s; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read-only"); + return (FALSE); + } + if ((f == TRUE) && ((s = setprefix(FFRAND, 1)) != TRUE)) + return (s); + + /* get # of lines to affect */ + if ((s = getregion(®ion)) != TRUE) + return (s); + first = region.r_linep; + last = (first == curwp->w_dotp) ? curwp->w_markp : curwp->w_dotp; + for (nline = 1; first != last; nline++) + first = lforw(first); + + /* move to beginning of region */ + curwp->w_dotp = region.r_linep; + curwp->w_doto = region.r_offset; + curwp->w_dotline = region.r_lineno; + + /* for each line, go to beginning and insert the prefix string */ + while (nline--) { + (void)gotobol(FFRAND, 1); + for (prefix = prefix_string; *prefix; prefix++) + (void)linsert(1, *prefix); + (void)forwline(FFRAND, 1); + } + (void)gotobol(FFRAND, 1); + return (TRUE); +} + +/* + * Set line prefix string. Used by prefixregion. + */ +/* ARGSUSED */ +int +setprefix(int f, int n) +{ + char buf[PREFIXLENGTH], *rep; + int retval; + + if (prefix_string[0] == '\0') + rep = eread("Prefix string: ", buf, sizeof(buf), + EFNEW | EFCR); + else + rep = eread("Prefix string (default %s): ", buf, sizeof(buf), + EFNUL | EFNEW | EFCR, prefix_string); + if (rep == NULL) + return (ABORT); + if (rep[0] != '\0') { + (void)strlcpy(prefix_string, rep, sizeof(prefix_string)); + retval = TRUE; + } else if (rep[0] == '\0' && prefix_string[0] != '\0') { + /* CR -- use old one */ + retval = TRUE; + } else + retval = FALSE; + return (retval); +} + +int +region_get_data(struct region *reg, char *buf, int len) +{ + int i, off; + struct line *lp; + + off = reg->r_offset; + lp = reg->r_linep; + for (i = 0; i < len; i++) { + if (off == llength(lp)) { + lp = lforw(lp); + if (lp == curbp->b_headp) + break; + off = 0; + buf[i] = '\n'; + } else { + buf[i] = lgetc(lp, off); + off++; + } + } + buf[i] = '\0'; + return (i); +} + +void +region_put_data(const char *buf, int len) +{ + int i; + + for (i = 0; buf[i] != '\0' && i < len; i++) { + if (buf[i] == '\n') + lnewline(); + else + linsert(1, buf[i]); + } +} + +/* + * Mark whole buffer by first traversing to end-of-buffer + * and then to beginning-of-buffer. Mark, dot are implicitly + * set to eob, bob respectively during traversal. + */ +int +markbuffer(int f, int n) +{ + if (gotoeob(f,n) == FALSE) + return (FALSE); + if (gotobob(f,n) == FALSE) + return (FALSE); + return (TRUE); +} + +/* + * Pipe text from current region to external command. + */ +/*ARGSUSED */ +int +piperegion(int f, int n) +{ + char *cmd, cmdbuf[NFILEN]; + + /* C-u M-| is not supported yet */ + if (n > 1) + return (ABORT); + + if (curwp->w_markp == NULL) { + ewprintf("The mark is not set now, so there is no region"); + return (FALSE); + } + if ((cmd = eread("Shell command on region: ", cmdbuf, sizeof(cmdbuf), + EFNEW | EFCR)) == NULL || (cmd[0] == '\0')) + return (ABORT); + + return (pipeio(cmdbuf)); +} + +/* + * Create a socketpair, fork and execl cmd passed. STDIN, STDOUT + * and STDERR of child process are redirected to socket. + */ +int +pipeio(const char* const cmd) +{ + int s[2]; + char *shellp; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) { + ewprintf("socketpair error"); + return (FALSE); + } + switch(fork()) { + case -1: + ewprintf("Can't fork"); + return (FALSE); + case 0: + /* Child process */ + close(s[0]); + if (dup2(s[1], STDIN_FILENO) == -1) + _exit(1); + if (dup2(s[1], STDOUT_FILENO) == -1) + _exit(1); + if (dup2(s[1], STDERR_FILENO) == -1) + _exit(1); + if ((shellp = getenv("SHELL")) == NULL) + _exit(1); + execl(shellp, "sh", "-c", cmd, (char *)NULL); + _exit(1); + default: + /* Parent process */ + close(s[1]); + return iomux(s[0]); + } + return (FALSE); +} + +/* + * Multiplex read, write on socket fd passed. First get the region, + * find/create *Shell Command Output* buffer and clear it's contents. + * Poll on the fd for both read and write readiness. + */ +int +iomux(int fd) +{ + struct region region; + struct buffer *bp; + struct pollfd pfd[1]; + int nfds; + char *text, *textcopy; + + if (getregion(®ion) != TRUE) + return (FALSE); + + if ((text = malloc(region.r_size + 1)) == NULL) + return (ABORT); + + region_get_data(®ion, text, region.r_size); + textcopy = text; + fcntl(fd, F_SETFL, O_NONBLOCK); + + /* There is nothing to write if r_size is zero + * but the cmd's output should be read so shutdown + * the socket for writing only. + */ + if (region.r_size == 0) + shutdown(fd, SHUT_WR); + + bp = bfind("*Shell Command Output*", TRUE); + bp->b_flag |= BFREADONLY; + if (bclear(bp) != TRUE) + return (FALSE); + + pfd[0].fd = fd; + pfd[0].events = POLLIN | POLLOUT; + while ((nfds = poll(pfd, 1, TIMEOUT)) != -1 || + (pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL))) { + if (pfd[0].revents & POLLOUT && region.r_size > 0) + pwriteout(fd, &textcopy, ®ion.r_size); + else if (pfd[0].revents & POLLIN) + if (preadin(fd, bp) == FALSE) + break; + } + close(fd); + free(text); + /* In case if last line doesn't have a '\n' add the leftover + * characters to buffer. + */ + if (leftover[0] != '\0') { + addline(bp, leftover); + leftover[0] = '\0'; + } + if (nfds == 0) { + ewprintf("poll timed out"); + return (FALSE); + } else if (nfds == -1) { + ewprintf("poll error"); + return (FALSE); + } + return (popbuftop(bp, WNONE)); +} + +/* + * Write some text from region to fd. Once done shutdown the + * write end. + */ +void +pwriteout(int fd, char **text, int *len) +{ + int w; + + if (((w = send(fd, *text, *len, MSG_NOSIGNAL)) == -1)) { + switch(errno) { + case EPIPE: + *len = -1; + break; + case EAGAIN: + return; + } + } else + *len -= w; + + *text += w; + if (*len <= 0) + shutdown(fd, SHUT_WR); +} + +/* + * Read some data from socket fd, break on '\n' and add + * to buffer. If couldn't break on newline hold leftover + * characters and append in next iteration. + */ +int +preadin(int fd, struct buffer *bp) +{ + int len; + static int nooutput; + char buf[BUFSIZ], *p, *q; + + if ((len = read(fd, buf, BUFSIZ - 1)) == 0) { + if (nooutput == 0) + addline(bp, "(Shell command succeeded with no output)"); + nooutput = 0; + return (FALSE); + } + nooutput = 1; + buf[len] = '\0'; + p = q = buf; + if (leftover[0] != '\0' && ((q = strchr(p, '\n')) != NULL)) { + *q++ = '\0'; + if (strlcat(leftover, p, sizeof(leftover)) >= + sizeof(leftover)) { + ewprintf("line too long"); + return (FALSE); + } + addline(bp, leftover); + leftover[0] = '\0'; + p = q; + } + while ((q = strchr(p, '\n')) != NULL) { + *q++ = '\0'; + addline(bp, p); + p = q; + } + if (strlcpy(leftover, p, sizeof(leftover)) >= sizeof(leftover)) { + ewprintf("line too long"); + return (FALSE); + } + return (TRUE); +} diff --git a/mg/spawn.c b/mg/spawn.c new file mode 100644 index 0000000..13fd985 --- /dev/null +++ b/mg/spawn.c @@ -0,0 +1,48 @@ +/* $OpenBSD: spawn.c,v 1.11 2006/08/01 22:16:03 jason Exp $ */ + +/* This file is in the public domain. */ + +/* + * Spawn. Actually just suspends Mg. + * Assumes POSIX job control. + */ + +#include "def.h" + +#include +#include + +/* + * This causes mg to send itself a stop signal. It assumes the parent + * shell supports POSIX job control. If the terminal supports an alternate + * screen, we will switch to it. + */ +/* ARGSUSED */ +int +spawncli(int f, int n) +{ + sigset_t oset; + + /* Very similar to what vttidy() does. */ + ttcolor(CTEXT); + ttnowindow(); + ttmove(nrow - 1, 0); + if (epresf != FALSE) { + tteeol(); + epresf = FALSE; + } + if (ttcooked() == FALSE) + return (FALSE); + + /* Exit application mode and tidy. */ + tttidy(); + ttflush(); + (void)sigprocmask(SIG_SETMASK, NULL, &oset); + (void)kill(0, SIGTSTP); + (void)sigprocmask(SIG_SETMASK, &oset, NULL); + ttreinit(); + + /* Force repaint. */ + sgarbf = TRUE; + return (ttraw()); +} diff --git a/mg/sysdef.h b/mg/sysdef.h new file mode 100644 index 0000000..6b75614 --- /dev/null +++ b/mg/sysdef.h @@ -0,0 +1,30 @@ +/* $OpenBSD: sysdef.h,v 1.16 2008/09/15 16:11:35 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * POSIX system header file + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define KBLOCK 8192 /* Kill grow. */ +#define GOOD 0 /* Good exit status. */ + +typedef int RSIZE; /* Type for file/region sizes */ +typedef short KCHAR; /* Type for internal keystrokes */ + +#define MALLOCROUND(m) (m+=7,m&=~7) /* round up to 8 byte boundary */ + +struct fileinfo { + uid_t fi_uid; + gid_t fi_gid; + mode_t fi_mode; + struct timespec fi_mtime; /* Last modified time */ +}; diff --git a/mg/tags.c b/mg/tags.c new file mode 100644 index 0000000..92d1425 --- /dev/null +++ b/mg/tags.c @@ -0,0 +1,533 @@ +/* $OpenBSD: tags.c,v 1.5 2012/07/02 08:08:31 lum Exp $ */ + +/* + * This file is in the public domain. + * + * Author: Sunil Nimmagadda + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "def.h" + +struct ctag; + +static int addctag(char *); +static int atbow(void); +void closetags(void); +static int ctagcmp(struct ctag *, struct ctag *); +static int loadbuffer(char *); +static int loadtags(const char *); +static int pushtag(char *); +static int searchpat(char *); +static struct ctag *searchtag(char *); +static char *strip(char *, size_t); +static void unloadtags(void); + +#define DEFAULTFN "tags" + +char *tagsfn = NULL; +int loaded = FALSE; + +/* ctags(1) entries are parsed and maintained in a tree. */ +struct ctag { + RB_ENTRY(ctag) entry; + char *tag; + char *fname; + char *pat; +}; +RB_HEAD(tagtree, ctag) tags = RB_INITIALIZER(&tags); +RB_GENERATE(tagtree, ctag, entry, ctagcmp); + +struct tagpos { + SLIST_ENTRY(tagpos) entry; + int doto; + int dotline; + char *bname; +}; +SLIST_HEAD(tagstack, tagpos) shead = SLIST_HEAD_INITIALIZER(shead); + +int +ctagcmp(struct ctag *s, struct ctag *t) +{ + return strcmp(s->tag, t->tag); +} + +/* + * Record the filename that contain tags to be used while loading them + * on first use. If a filename is already recorded, ask user to retain + * already loaded tags (if any) and unload them if user chooses not to. + */ +/* ARGSUSED */ +int +tagsvisit(int f, int n) +{ + char fname[NFILEN], *bufp, *temp; + struct stat sb; + + if (getbufcwd(fname, sizeof(fname)) == FALSE) + fname[0] = '\0'; + + if (strlcat(fname, DEFAULTFN, sizeof(fname)) >= sizeof(fname)) { + ewprintf("Filename too long"); + return (FALSE); + } + + bufp = eread("visit tags table (default %s): ", fname, + NFILEN, EFFILE | EFCR | EFNEW | EFDEF, DEFAULTFN); + + if (stat(bufp, &sb) == -1) { + ewprintf("stat: %s", strerror(errno)); + return (FALSE); + } else if (S_ISREG(sb.st_mode) == 0) { + ewprintf("Not a regular file"); + return (FALSE); + } else if (access(bufp, R_OK) == -1) { + ewprintf("Cannot access file %s", bufp); + return (FALSE); + } + + if (tagsfn == NULL) { + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') { + if ((tagsfn = strdup(fname)) == NULL) { + ewprintf("Out of memory"); + return (FALSE); + } + } else { + /* bufp points to local variable, so duplicate. */ + if ((tagsfn = strdup(bufp)) == NULL) { + ewprintf("Out of memory"); + return (FALSE); + } + } + } else { + if ((temp = strdup(bufp)) == NULL) { + ewprintf("Out of memory"); + return (FALSE); + } + free(tagsfn); + tagsfn = temp; + if (eyorn("Keep current list of tags table also") == FALSE) { + ewprintf("Starting a new list of tags table"); + unloadtags(); + } + loaded = FALSE; + } + return (TRUE); +} + +/* + * Ask user for a tag while treating word at dot as default. Visit tags + * file if not yet done, load tags and jump to definition of the tag. + */ +int +findtag(int f, int n) +{ + char utok[MAX_TOKEN], dtok[MAX_TOKEN]; + char *tok, *bufp; + int ret; + + if (curtoken(f, n, dtok) == FALSE) + return (FALSE); + + bufp = eread("Find tag (default %s) ", utok, MAX_TOKEN, + EFNUL | EFNEW, dtok); + + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + tok = dtok; + else + tok = utok; + + if (tok[0] == '\0') { + ewprintf("There is no default tag"); + return (FALSE); + } + + if (tagsfn == NULL) + if ((ret = tagsvisit(f, n)) != TRUE) + return (ret); + if (!loaded) { + if (loadtags(tagsfn) == FALSE) { + free(tagsfn); + tagsfn = NULL; + return (FALSE); + } + loaded = TRUE; + } + return pushtag(tok); +} + +/* + * Free tags tree. + */ +void +unloadtags(void) +{ + struct ctag *var, *nxt; + + for (var = RB_MIN(tagtree, &tags); var != NULL; var = nxt) { + nxt = RB_NEXT(tagtree, &tags, var); + RB_REMOVE(tagtree, &tags, var); + /* line parsed with fparseln needs to be freed */ + free(var->tag); + free(var); + } +} + +/* + * Lookup tag passed in tree and if found, push current location and + * buffername onto stack, load the file with tag definition into a new + * buffer and position dot at the pattern. + */ +/*ARGSUSED */ +int +pushtag(char *tok) +{ + struct ctag *res; + struct tagpos *s; + char bname[NFILEN]; + int doto, dotline; + + if ((res = searchtag(tok)) == NULL) + return (FALSE); + + doto = curwp->w_doto; + dotline = curwp->w_dotline; + /* record absolute filenames. Fixes issues when mg's cwd is not the + * same as buffer's directory. + */ + if (strlcpy(bname, curbp->b_cwd, sizeof(bname)) >= sizeof(bname)) { + ewprintf("filename too long"); + return (FALSE); + } + if (strlcat(bname, curbp->b_bname, sizeof(bname)) >= sizeof(bname)) { + ewprintf("filename too long"); + return (FALSE); + } + + if (loadbuffer(res->fname) == FALSE) + return (FALSE); + + if (searchpat(res->pat) == TRUE) { + if ((s = malloc(sizeof(struct tagpos))) == NULL) { + ewprintf("Out of memory"); + return (FALSE); + } + if ((s->bname = strdup(bname)) == NULL) { + ewprintf("Out of memory"); + return (FALSE); + } + s->doto = doto; + s->dotline = dotline; + SLIST_INSERT_HEAD(&shead, s, entry); + return (TRUE); + } else { + ewprintf("%s: pattern not found", res->tag); + return (FALSE); + } + /* NOTREACHED */ + return (FALSE); +} + +/* + * If tag stack is not empty pop stack and jump to recorded buffer, dot. + */ +/* ARGSUSED */ +int +poptag(int f, int n) +{ + struct line *dotp; + struct tagpos *s; + + if (SLIST_EMPTY(&shead)) { + ewprintf("No previous location for find-tag invocation"); + return (FALSE); + } + s = SLIST_FIRST(&shead); + SLIST_REMOVE_HEAD(&shead, entry); + if (loadbuffer(s->bname) == FALSE) + return (FALSE); + curwp->w_dotline = s->dotline; + curwp->w_doto = s->doto; + + /* storing of dotp in tagpos wouldn't work out in cases when + * that buffer is killed by user(dangling pointer). Explicitly + * traverse till dotline for correct handling. + */ + dotp = curwp->w_bufp->b_headp; + while (s->dotline--) + dotp = dotp->l_fp; + + curwp->w_dotp = dotp; + free(s->bname); + free(s); + return (TRUE); +} + +/* + * Parse the tags file and construct the tags tree. Remove escape + * characters while parsing the file. + */ +int +loadtags(const char *fn) +{ + char *l; + FILE *fd; + + if ((fd = fopen(fn, "r")) == NULL) { + ewprintf("Unable to open tags file: %s", fn); + return (FALSE); + } + while ((l = fparseln(fd, NULL, NULL, "\\\\\0", + FPARSELN_UNESCCONT | FPARSELN_UNESCREST)) != NULL) { + if (addctag(l) == FALSE) { + fclose(fd); + return (FALSE); + } + } + fclose(fd); + return (TRUE); +} + +/* + * Cleanup and destroy tree and stack. + */ +void +closetags(void) +{ + struct tagpos *s; + + while (!SLIST_EMPTY(&shead)) { + s = SLIST_FIRST(&shead); + SLIST_REMOVE_HEAD(&shead, entry); + free(s->bname); + free(s); + } + unloadtags(); + free(tagsfn); +} + +/* + * Strip away any special characters in pattern. + * The pattern in ctags isn't a true regular expression. Its of the form + * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip + * the leading and trailing special characters so the pattern matching + * would be a simple string compare. Escape character is taken care by + * fparseln. + */ +char * +strip(char *s, size_t len) +{ + /* first strip trailing special chars */ + s[len - 1] = '\0'; + if (s[len - 2] == '$') + s[len - 2] = '\0'; + + /* then strip leading special chars */ + s++; + if (*s == '^') + s++; + + return s; +} + +/* + * tags line is of the format "\t\t". Split them + * by replacing '\t' with '\0'. This wouldn't alter the size of malloc'ed + * l, and can be freed during cleanup. + */ +int +addctag(char *l) +{ + struct ctag *t; + + if ((t = malloc(sizeof(struct ctag))) == NULL) { + ewprintf("Out of memory"); + return (FALSE); + } + t->tag = l; + if ((l = strchr(l, '\t')) == NULL) + goto cleanup; + *l++ = '\0'; + t->fname = l; + if ((l = strchr(l, '\t')) == NULL) + goto cleanup; + *l++ = '\0'; + if (*l == '\0') + goto cleanup; + t->pat = strip(l, strlen(l)); + RB_INSERT(tagtree, &tags, t); + return (TRUE); +cleanup: + free(t); + free(l); + return (TRUE); +} + +/* + * Search through each line of buffer for pattern. + */ +int +searchpat(char *pat) +{ + struct line *lp; + int dotline; + size_t plen; + + plen = strlen(pat); + dotline = 1; + lp = lforw(curbp->b_headp); + while (lp != curbp->b_headp) { + if (ltext(lp) != NULL && plen <= llength(lp) && + (strncmp(pat, ltext(lp), plen) == 0)) { + curwp->w_doto = 0; + curwp->w_dotp = lp; + curwp->w_dotline = dotline; + return (TRUE); + } else { + lp = lforw(lp); + dotline++; + } + } + return (FALSE); +} + +/* + * Return TRUE if dot is at beginning of a word or at beginning + * of line, else FALSE. + */ +int +atbow(void) +{ + if (curwp->w_doto == 0) + return (TRUE); + if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) && + !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1])) + return (TRUE); + return (FALSE); +} + +/* + * Extract the word at dot without changing dot position. + */ +int +curtoken(int f, int n, char *token) +{ + struct line *odotp; + int odoto, tdoto, odotline, size, r; + char c; + + /* Underscore character is to be treated as "inword" while + * processing tokens unlike mg's default word traversal. Save + * and restore it's cinfo value so that tag matching works for + * identifier with underscore. + */ + c = cinfo['_']; + cinfo['_'] = _MG_W; + + odotp = curwp->w_dotp; + odoto = curwp->w_doto; + odotline = curwp->w_dotline; + + /* Move backword unless we are at the beginning of a word or at + * beginning of line. + */ + if (!atbow()) + if ((r = backword(f, n)) == FALSE) + goto cleanup; + + tdoto = curwp->w_doto; + + if ((r = forwword(f, n)) == FALSE) + goto cleanup; + + /* strip away leading whitespace if any like emacs. */ + while (ltext(curwp->w_dotp) && + isspace(curwp->w_dotp->l_text[tdoto])) + tdoto++; + + size = curwp->w_doto - tdoto; + if (size <= 0 || size >= MAX_TOKEN || + ltext(curwp->w_dotp) == NULL) { + r = FALSE; + goto cleanup; + } + strncpy(token, ltext(curwp->w_dotp) + tdoto, size); + token[size] = '\0'; + r = TRUE; + +cleanup: + cinfo['_'] = c; + curwp->w_dotp = odotp; + curwp->w_doto = odoto; + curwp->w_dotline = odotline; + return (r); +} + +/* + * Search tagstree for a given token. + */ +struct ctag * +searchtag(char *tok) +{ + struct ctag t, *res; + + t.tag = tok; + if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) { + ewprintf("No tag containing %s", tok); + return (NULL); + } + return res; +} + +/* + * This is equivalent to filevisit from file.c. + * Look around to see if we can find the file in another buffer; if we + * can't find it, create a new buffer, read in the text, and switch to + * the new buffer. *scratch*, *grep*, *compile* needs to be handled + * differently from other buffers which have "filenames". + */ +int +loadbuffer(char *bname) +{ + struct buffer *bufp; + char *adjf; + + /* check for special buffers which begin with '*' */ + if (bname[0] == '*') { + if ((bufp = bfind(bname, FALSE)) != NULL) { + curbp = bufp; + return (showbuffer(bufp, curwp, WFFULL)); + } else { + return (FALSE); + } + } else { + if ((adjf = adjustname(bname, TRUE)) == NULL) + return (FALSE); + if ((bufp = findbuffer(adjf)) == NULL) + return (FALSE); + } + curbp = bufp; + if (showbuffer(bufp, curwp, WFFULL) != TRUE) + return (FALSE); + if (bufp->b_fname[0] == '\0') { + if (readin(adjf) != TRUE) { + killbuffer(bufp); + return (FALSE); + } + } + return (TRUE); +} diff --git a/mg/theo.c b/mg/theo.c new file mode 100644 index 0000000..408f9e1 --- /dev/null +++ b/mg/theo.c @@ -0,0 +1,192 @@ +/* $OpenBSD: theo.c,v 1.124 2012/07/09 22:24:36 mlarkin Exp $ */ +/* + * Copyright (c) 2002 Artur Grabowski + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "def.h" +#include "kbd.h" +#include "funmap.h" + +void theo_init(void); +static int theo_analyze(int, int); +static int theo(int, int); + +static PF theo_pf[] = { + theo_analyze +}; + +static struct KEYMAPE (1 + IMAPEXT) theomap = { + 1, + 1 + IMAPEXT, + rescan, + { + { CCHR('M'), CCHR('M'), theo_pf, NULL } + } +}; + +void +theo_init(void) +{ + funmap_add(theo, "theo"); + maps_add((KEYMAP *)&theomap, "theo"); +} + +/* ARGSUSED */ +static int +theo(int f, int n) +{ + struct buffer *bp; + struct mgwin *wp; + + bp = bfind("theo", TRUE); + if (bclear(bp) != TRUE) + return (FALSE); + + bp->b_modes[0] = name_mode("fundamental"); + bp->b_modes[1] = name_mode("theo"); + bp->b_nmodes = 1; + + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + + curbp = bp; + curwp = wp; + onlywind(f, n); + + return (TRUE); +} + +static const char *talk[] = { + "Write more code.", + "Make more commits.", + "That's because you have been slacking.", + "slacker!", + "That's what happens when you're lazy.", + "idler!", + "slackass!", + "lazy bum!", + "Stop slacking you lazy bum!", + "slacker slacker lazy bum bum bum slacker!", + "I could search... but I'm a lazy bum ;)", + "sshutup sshithead, ssharpsshooting susshi sshplats ssharking assholes.", + "Lazy bums slacking on your asses.", + "35 commits an hour? That's pathetic!", + "Fine software takes time to prepare. Give a little slack.", + "I am just stating a fact", + "you bring new meaning to the terms slackass. I will have to invent a new term.", + "if they cut you out, muddy their back yards", + "Make them want to start over, and play nice the next time.", + "It is clear that this has not been thought through.", + "avoid using abort(). it is not nice.", + "That's the most ridiculous thing I've heard in the last two or three minutes!", + "I'm not just doing this for crowd response. I need to be right.", + "I'd put a fan on my bomb.. And blinking lights...", + "I love to fight", + "No sane people allowed here. Go home.", + "you have to stop peeing on your breakfast", + "feature requests come from idiots", + "henning and darren / sitting in a tree / t o k i n g / a joint or three", + "KICK ASS. TIME FOR A JASON LOVE IN! WE CAN ALL GET LOST IN HIS HAIR!", + "shame on you for following my rules.", + "altq's parser sucks dead whale farts through the finest chemistry pipette's", + "screw this operating system shit, i just want to drive!", + "Search for fuck. Anytime you see that word, you have a paragraph to write.", + "Yes, but the ports people are into S&M.", + "Buttons are for idiots.", + "We are not hackers. We are turd polishing craftsmen.", + "who cares. style(9) can bite my ass", + "It'd be one fucking happy planet if it wasn't for what's under this fucking sticker.", + "I would explain, but I am too drunk.", + "you slackers don't deserve pictures yet", + "Vegetarian my ass", + "Wait a minute, that's a McNally's!", + "don't they recognize their moral responsibility to entertain me?", + "#ifdef is for emacs developers.", + "Many well known people become net-kooks in their later life, because they lose touch with reality.", + "You're not allowed to have an opinion.", + "tweep tweep tweep", + "Quite frankly, SSE's alignment requirement is the most utterly retarded idea since eating your own shit.", + "Holy verbose prom startup Batman.", + "Any day now, when we sell out.", + "optimism in man kind does not belong here", + "First user who tries to push this button, he pounds into the ground with a rant of death.", + "we did farts. now we do sperm. we are cutting edge.", + "the default configuration is a mixture of piss, puke, shit, and bloody entrails.", + "Stop wasting your time reading people's licenses.", + "doing it with environment variables is OH SO SYSTEM FIVE LIKE OH MY GOD PASS ME THE SPOON", + "Linux is fucking POO, not just bad, bad REALLY REALLY BAD", + "penguins are not much more than chickens that swim.", + "i am a packet sniffing fool, let me wipe my face with my own poo", + "Whiners. They scale really well.", + "in your world, you would have a checklist of 50 fucking workarounds just to make a coffee.", + "for once, I have nothing to say.", + "You have no idea how fucked we are", + "You can call it fart if you want to.", + "wavelan is a battle field", + "You are in a maze of gpio pins, all alike, all undocumented, and a few are wired to bombs.", + "And that is why humppa sucks... cause it has no cause.", + "cache aliasing is a problem that would have stopped in 1992 if someone had killed about 5 people who worked at Sun.", + "Don't spread rumours about me being gentle.", + "If municipal water filtering equipment was built by the gcc developers, the western world would be dead by now.", + "kettenis supported a new machine in my basement and all I got to do was fix a 1 character typo in his html page commit.", + "industry told us a lesson: when you're an asshole, they mail you hardware", + "I was joking, really. I think I am funny :-)", + "the kernel is a harsh mistress", + "Have I ever been subtle? If my approach ever becomes subtle, shoot me.", + "the acpi stabs you in the back. the acpi stabs you in the back. you die ...", + "My cats are more observant than you.", + "our kernels have no bugs", + "style(9) has all these fascist rules, and i have a problem with some of them because i didn't come up with them", + "I'm not very reliable", + "I don't like control", + "You aren't being conservative -- you are trying to be a caveman.", + "nfs loves everyone", + "basically, dung beetles fucking. that's what kerberosV + openssl is like", + "I would rather run Windows than use vi.", + "if you assign that responsibility to non-hikers I will walk over and cripple you now.", + "i ojbect two yoru splelng of achlhlocis.", + "We have two kinds of developers - those that deal with their own shit and those that deal with other people's shit." +}; + +static const int ntalk = sizeof(talk)/sizeof(talk[0]); + +/* ARGSUSED */ +static int +theo_analyze(int f, int n) +{ + const char *str; + int len; + + str = talk[arc4random_uniform(ntalk)]; + len = strlen(str); + + newline(FFRAND, 2); + + while (len--) + linsert(1, *str++); + + newline(FFRAND, 2); + + return (TRUE); +} diff --git a/mg/tty.c b/mg/tty.c new file mode 100644 index 0000000..f803cc7 --- /dev/null +++ b/mg/tty.c @@ -0,0 +1,447 @@ +/* $OpenBSD: tty.c,v 1.30 2008/09/15 16:11:35 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * Terminfo display driver + * + * Terminfo is a terminal information database and routines to describe + * terminals on most modern UNIX systems. Many other systems have adopted + * this as a reasonable way to allow for widely varying and ever changing + * varieties of terminal types. This should be used where practical. + */ +/* + * Known problems: If you have a terminal with no clear to end of screen and + * memory of lines below the ones visible on the screen, display will be + * wrong in some cases. I doubt that any such terminal was ever made, but I + * thought everyone with delete line would have clear to end of screen too... + * + * Code for terminals without clear to end of screen and/or clear to end of line + * has not been extensively tested. + * + * Cost calculations are very rough. Costs of insert/delete line may be far + * from the truth. This is accentuated by display.c not knowing about + * multi-line insert/delete. + * + * Using scrolling region vs insert/delete line should probably be based on cost + * rather than the assumption that scrolling region operations look better. + */ + +#include "def.h" + +#include +#include +#include + +#include + +static int charcost(char *); + +static int cci; +static int insdel; /* Do we have both insert & delete line? */ +static char *scroll_fwd; /* How to scroll forward. */ + +static void winchhandler(int); + +/* ARGSUSED */ +static void +winchhandler(int sig) +{ + winch_flag = 1; +} + +/* + * Initialize the terminal when the editor + * gets started up. + */ +void +ttinit(void) +{ + int errret; + + if (setupterm(NULL, 1, &errret)) + panic("Terminal setup failed"); + + signal(SIGWINCH, winchhandler); + signal(SIGCONT, winchhandler); + siginterrupt(SIGWINCH, 1); + + scroll_fwd = scroll_forward; + if (scroll_fwd == NULL || *scroll_fwd == '\0') { + /* this is what GNU Emacs does */ + scroll_fwd = parm_down_cursor; + if (scroll_fwd == NULL || *scroll_fwd == '\0') + scroll_fwd = "\n"; + } + + if (cursor_address == NULL || cursor_up == NULL) + panic("This terminal is too stupid to run mg"); + + /* set nrow & ncol */ + ttresize(); + + if (!clr_eol) + tceeol = ncol; + else + tceeol = charcost(clr_eol); + + /* Estimate cost of inserting a line */ + if (change_scroll_region && scroll_reverse) + tcinsl = charcost(change_scroll_region) * 2 + + charcost(scroll_reverse); + else if (parm_insert_line) + tcinsl = charcost(parm_insert_line); + else if (insert_line) + tcinsl = charcost(insert_line); + else + /* make this cost high enough */ + tcinsl = nrow * ncol; + + /* Estimate cost of deleting a line */ + if (change_scroll_region) + tcdell = charcost(change_scroll_region) * 2 + + charcost(scroll_fwd); + else if (parm_delete_line) + tcdell = charcost(parm_delete_line); + else if (delete_line) + tcdell = charcost(delete_line); + else + /* make this cost high enough */ + tcdell = nrow * ncol; + + /* Flag to indicate that we can both insert and delete lines */ + insdel = (insert_line || parm_insert_line) && + (delete_line || parm_delete_line); + + if (enter_ca_mode) + /* enter application mode */ + putpad(enter_ca_mode, 1); + + ttresize(); +} + +/* + * Re-initialize the terminal when the editor is resumed. + * The keypad_xmit doesn't really belong here but... + */ +void +ttreinit(void) +{ + /* check if file was modified while we were gone */ + if (fchecktime(curbp) != TRUE) { + curbp->b_flag |= BFDIRTY; + } + + if (enter_ca_mode) + /* enter application mode */ + putpad(enter_ca_mode, 1); + + if (keypad_xmit) + /* turn on keypad */ + putpad(keypad_xmit, 1); + + ttresize(); +} + +/* + * Clean up the terminal, in anticipation of a return to the command + * interpreter. This is a no-op on the ANSI display. On the SCALD display, + * it sets the window back to half screen scrolling. Perhaps it should + * query the display for the increment, and put it back to what it was. + */ +void +tttidy(void) +{ +#ifdef XKEYS + ttykeymaptidy(); +#endif /* XKEYS */ + + /* set the term back to normal mode */ + if (exit_ca_mode) + putpad(exit_ca_mode, 1); +} + +/* + * Move the cursor to the specified origin 0 row and column position. Try to + * optimize out extra moves; redisplay may have left the cursor in the right + * location last time! + */ +void +ttmove(int row, int col) +{ + if (ttrow != row || ttcol != col) { + putpad(tgoto(cursor_address, col, row), 1); + ttrow = row; + ttcol = col; + } +} + +/* + * Erase to end of line. + */ +void +tteeol(void) +{ + int i; + + if (clr_eol) + putpad(clr_eol, 1); + else { + i = ncol - ttcol; + while (i--) + ttputc(' '); + ttrow = ttcol = HUGE; + } +} + +/* + * Erase to end of page. + */ +void +tteeop(void) +{ + int line; + + if (clr_eos) + putpad(clr_eos, nrow - ttrow); + else { + putpad(clr_eol, 1); + if (insdel) + ttdell(ttrow + 1, lines, lines - ttrow - 1); + else { + /* do it by hand */ + for (line = ttrow + 1; line <= lines; ++line) { + ttmove(line, 0); + tteeol(); + } + } + ttrow = ttcol = HUGE; + } +} + +/* + * Make a noise. + */ +void +ttbeep(void) +{ + putpad(bell, 1); + ttflush(); +} + +/* + * Insert nchunk blank line(s) onto the screen, scrolling the last line on + * the screen off the bottom. Use the scrolling region if possible for a + * smoother display. If there is no scrolling region, use a set of insert + * and delete line sequences. + */ +void +ttinsl(int row, int bot, int nchunk) +{ + int i, nl; + + /* Case of one line insert is special. */ + if (row == bot) { + ttmove(row, 0); + tteeol(); + return; + } + if (change_scroll_region && scroll_reverse) { + /* Use scroll region and back index */ + nl = bot - row; + ttwindow(row, bot); + ttmove(row, 0); + while (nchunk--) + putpad(scroll_reverse, nl); + ttnowindow(); + return; + } else if (insdel) { + ttmove(1 + bot - nchunk, 0); + nl = nrow - ttrow; + if (parm_delete_line) + putpad(tgoto(parm_delete_line, 0, nchunk), nl); + else + /* For all lines in the chunk... */ + for (i = 0; i < nchunk; i++) + putpad(delete_line, nl); + ttmove(row, 0); + + /* ttmove() changes ttrow */ + nl = nrow - ttrow; + + if (parm_insert_line) + putpad(tgoto(parm_insert_line, 0, nchunk), nl); + else + /* For all lines in the chunk */ + for (i = 0; i < nchunk; i++) + putpad(insert_line, nl); + ttrow = HUGE; + ttcol = HUGE; + } else + panic("ttinsl: Can't insert/delete line"); +} + +/* + * Delete nchunk line(s) from "row", replacing the bottom line on the + * screen with a blank line. Unless we're using the scrolling region, + * this is done with crafty sequences of insert and delete lines. The + * presence of the echo area makes a boundary condition go away. + */ +void +ttdell(int row, int bot, int nchunk) +{ + int i, nl; + + /* One line special cases */ + if (row == bot) { + ttmove(row, 0); + tteeol(); + return; + } + /* scrolling region */ + if (change_scroll_region) { + nl = bot - row; + ttwindow(row, bot); + ttmove(bot, 0); + while (nchunk--) + putpad(scroll_fwd, nl); + ttnowindow(); + /* else use insert/delete line */ + } else if (insdel) { + ttmove(row, 0); + nl = nrow - ttrow; + if (parm_delete_line) + putpad(tgoto(parm_delete_line, 0, nchunk), nl); + else + /* For all lines in the chunk */ + for (i = 0; i < nchunk; i++) + putpad(delete_line, nl); + ttmove(1 + bot - nchunk, 0); + + /* ttmove() changes ttrow */ + nl = nrow - ttrow; + if (parm_insert_line) + putpad(tgoto(parm_insert_line, 0, nchunk), nl); + else + /* For all lines in the chunk */ + for (i = 0; i < nchunk; i++) + putpad(insert_line, nl); + ttrow = HUGE; + ttcol = HUGE; + } else + panic("ttdell: Can't insert/delete line"); +} + +/* + * This routine sets the scrolling window on the display to go from line + * "top" to line "bot" (origin 0, inclusive). The caller checks for the + * pathological 1-line scroll window which doesn't work right and avoids + * it. The "ttrow" and "ttcol" variables are set to a crazy value to + * ensure that the next call to "ttmove" does not turn into a no-op (the + * window adjustment moves the cursor). + */ +void +ttwindow(int top, int bot) +{ + if (change_scroll_region && (tttop != top || ttbot != bot)) { + putpad(tgoto(change_scroll_region, bot, top), nrow - ttrow); + ttrow = HUGE; /* Unknown. */ + ttcol = HUGE; + tttop = top; /* Remember region. */ + ttbot = bot; + } +} + +/* + * Switch to full screen scroll. This is used by "spawn.c" just before it + * suspends the editor and by "display.c" when it is getting ready to + * exit. This function does a full screen scroll by telling the terminal + * to set a scrolling region that is lines or nrow rows high, whichever is + * larger. This behavior seems to work right on systems where you can set + * your terminal size. + */ +void +ttnowindow(void) +{ + if (change_scroll_region) { + putpad(tgoto(change_scroll_region, + (nrow > lines ? nrow : lines) - 1, 0), nrow - ttrow); + ttrow = HUGE; /* Unknown. */ + ttcol = HUGE; + tttop = HUGE; /* No scroll region. */ + ttbot = HUGE; + } +} + +/* + * Set the current writing color to the specified color. Watch for color + * changes that are not going to do anything (the color is already right) + * and don't send anything to the display. The rainbow version does this + * in putline.s on a line by line basis, so don't bother sending out the + * color shift. + */ +void +ttcolor(int color) +{ + if (color != tthue) { + if (color == CTEXT) + /* normal video */ + putpad(exit_standout_mode, 1); + else if (color == CMODE) + /* reverse video */ + putpad(enter_standout_mode, 1); + /* save the color */ + tthue = color; + } +} + +/* + * This routine is called by the "refresh the screen" command to try + * to resize the display. Look in "window.c" to see how + * the caller deals with a change. + * + * We use `newrow' and `newcol' so vtresize() know the difference between the + * new and old settings. + */ +void +ttresize(void) +{ + int newrow = 0, newcol = 0; + +#ifdef TIOCGWINSZ + struct winsize winsize; + + if (ioctl(0, TIOCGWINSZ, &winsize) == 0) { + newrow = winsize.ws_row; + newcol = winsize.ws_col; + } +#endif + if ((newrow <= 0 || newcol <= 0) && + ((newrow = lines) <= 0 || (newcol = columns) <= 0)) { + newrow = 24; + newcol = 80; + } + if (vtresize(1, newrow, newcol) != TRUE) + panic("vtresize failed"); +} + +/* + * fake char output for charcost() + */ +/* ARGSUSED */ +static int +fakec(int c) +{ + cci++; + return (0); +} + +/* calculate the cost of doing string s */ +static int +charcost(char *s) +{ + cci = 0; + + tputs(s, nrow, fakec); + return (cci); +} diff --git a/mg/ttydef.h b/mg/ttydef.h new file mode 100644 index 0000000..b32b1c0 --- /dev/null +++ b/mg/ttydef.h @@ -0,0 +1,25 @@ +/* $OpenBSD: ttydef.h,v 1.10 2005/11/20 03:53:45 deraadt Exp $ */ + +/* This file is in the public domain. */ + +#ifndef TTYDEF_H +#define TTYDEF_H + +/* + * Terminfo terminal file, nothing special, just make it big + * enough for windowing systems. + */ + +#define STANDOUT_GLITCH /* possible standout glitch */ +#define TERMCAP /* for possible use in ttyio.c */ + +#ifdef undef +#define MOVE_STANDOUT /* don't move in standout mode */ +#endif /* undef */ + +#define putpad(str, num) tputs(str, num, ttputc) + +#define KFIRST K00 +#define KLAST K00 + +#endif /* TTYDEF_H */ diff --git a/mg/ttyio.c b/mg/ttyio.c new file mode 100644 index 0000000..228a370 --- /dev/null +++ b/mg/ttyio.c @@ -0,0 +1,225 @@ +/* $OpenBSD: ttyio.c,v 1.32 2008/02/05 12:53:38 reyk Exp $ */ + +/* This file is in the public domain. */ + +/* + * POSIX terminal I/O. + * + * The functions in this file negotiate with the operating system for + * keyboard characters, and write characters to the display in a barely + * buffered fashion. + */ +#include "def.h" + +#include +#include +#include +#include +#include +#include + +#define NOBUF 512 /* Output buffer size. */ + +#ifndef TCSASOFT +#define TCSASOFT 0 +#endif + +int ttstarted; +char obuf[NOBUF]; /* Output buffer. */ +size_t nobuf; /* Buffer count. */ +struct termios oldtty; /* POSIX tty settings. */ +struct termios newtty; +int nrow; /* Terminal size, rows. */ +int ncol; /* Terminal size, columns. */ + +/* + * This function gets called once, to set up the terminal. + * On systems w/o TCSASOFT we turn off off flow control, + * which isn't really the right thing to do. + */ +void +ttopen(void) +{ + if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) + panic("standard input and output must be a terminal"); + + if (ttraw() == FALSE) + panic("aborting due to terminal initialize failure"); +} + +/* + * This function sets the terminal to RAW mode, as defined for the current + * shell. This is called both by ttopen() above and by spawncli() to + * get the current terminal settings and then change them to what + * mg expects. Thus, tty changes done while spawncli() is in effect + * will be reflected in mg. + */ +int +ttraw(void) +{ + if (tcgetattr(0, &oldtty) < 0) { + ewprintf("ttopen can't get terminal attributes"); + return (FALSE); + } + (void)memcpy(&newtty, &oldtty, sizeof(newtty)); + /* Set terminal to 'raw' mode and ignore a 'break' */ + newtty.c_cc[VMIN] = 1; + newtty.c_cc[VTIME] = 0; + newtty.c_iflag |= IGNBRK; + newtty.c_iflag &= ~(BRKINT | PARMRK | INLCR | IGNCR | ICRNL | IXON); + newtty.c_oflag &= ~OPOST; + newtty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + +#if !TCSASOFT + /* + * If we don't have TCSASOFT, force terminal to + * 8 bits, no parity. + */ + newtty.c_iflag &= ~ISTRIP; + newtty.c_cflag &= ~(CSIZE | PARENB); + newtty.c_cflag |= CS8; +#endif + if (tcsetattr(0, TCSASOFT | TCSADRAIN, &newtty) < 0) { + ewprintf("ttopen can't tcsetattr"); + return (FALSE); + } + ttstarted = 1; + + return (TRUE); +} + +/* + * This function gets called just before we go back home to the shell. + * Put all of the terminal parameters back. + * Under UN*X this just calls ttcooked(), but the ttclose() hook is in + * because vttidy() in display.c expects it for portability reasons. + */ +void +ttclose(void) +{ + if (ttstarted) { + if (ttcooked() == FALSE) + panic(""); /* ttcooked() already printf'd */ + ttstarted = 0; + } +} + +/* + * This function restores all terminal settings to their default values, + * in anticipation of exiting or suspending the editor. + */ +int +ttcooked(void) +{ + ttflush(); + if (tcsetattr(0, TCSASOFT | TCSADRAIN, &oldtty) < 0) { + ewprintf("ttclose can't tcsetattr"); + return (FALSE); + } + return (TRUE); +} + +/* + * Write character to the display. Characters are buffered up, + * to make things a little bit more efficient. + */ +int +ttputc(int c) +{ + if (nobuf >= NOBUF) + ttflush(); + obuf[nobuf++] = c; + return (c); +} + +/* + * Flush output. + */ +void +ttflush(void) +{ + ssize_t written; + char *buf = obuf; + + if (nobuf == 0) + return; + + while ((written = write(fileno(stdout), buf, nobuf)) != nobuf) { + if (written == -1) { + if (errno == EINTR) + continue; + panic("ttflush write failed"); + } + buf += written; + nobuf -= written; + } + nobuf = 0; +} + +/* + * Read character from terminal. All 8 bits are returned, so that you + * can use a multi-national terminal. + */ +int +ttgetc(void) +{ + char c; + ssize_t ret; + + do { + ret = read(STDIN_FILENO, &c, 1); + if (ret == -1 && errno == EINTR) { + if (winch_flag) { + redraw(0, 0); + winch_flag = 0; + } + } else if (ret == 1) + break; + } while (1); + return ((int) c) & 0xFF; +} + +/* + * Returns TRUE if there are characters waiting to be read. + */ +int +charswaiting(void) +{ + int x; + + return ((ioctl(0, FIONREAD, &x) < 0) ? 0 : x); +} + +/* + * panic - just exit, as quickly as we can. + */ +void +panic(char *s) +{ + ttclose(); + (void) fputs("panic: ", stderr); + (void) fputs(s, stderr); + (void) fputc('\n', stderr); + exit(1); +} + +/* + * This function returns FALSE if any characters have showed up on the + * tty before 'msec' milliseconds. + */ +int +ttwait(int msec) +{ + fd_set readfds; + struct timeval tmout; + + FD_ZERO(&readfds); + FD_SET(0, &readfds); + + tmout.tv_sec = msec/1000; + tmout.tv_usec = msec - tmout.tv_sec * 1000; + + if ((select(1, &readfds, NULL, NULL, &tmout)) == 0) + return (TRUE); + return (FALSE); +} diff --git a/mg/ttykbd.c b/mg/ttykbd.c new file mode 100644 index 0000000..a160a3e --- /dev/null +++ b/mg/ttykbd.c @@ -0,0 +1,89 @@ +/* $OpenBSD: ttykbd.c,v 1.16 2012/04/12 04:47:59 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Name: MG 2a + * Terminfo keyboard driver using key files + * Created: 22-Nov-1987 Mic Kaczmarczik (mic@emx.cc.utexas.edu) + */ + +#include "def.h" +#include "kbd.h" + +#ifdef XKEYS +#include + +#ifdef FKEYS +/* + * Get keyboard character. Very simple if you use keymaps and keys files. + * Bob was right -- the old XKEYS code is not the right solution. + * FKEYS code is not useful other than to help debug FKEYS code in + * extend.c. + */ + +char *keystrings[] = {NULL}; +#endif /* FKEYS */ + +/* + * Turn on function keys using keypad_xmit, then load a keys file, if + * available. The keys file is located in the same manner as the startup + * file is, depending on what startupfile() does on your system. + */ +void +ttykeymapinit(void) +{ + char *cp; + +#ifdef FKEYS + /* Bind keypad function keys. */ + if (key_left) + dobindkey(fundamental_map, "backward-char", key_left); + if (key_right) + dobindkey(fundamental_map, "forward-char", key_right); + if (key_up) + dobindkey(fundamental_map, "previous-line", key_up); + if (key_down) + dobindkey(fundamental_map, "next-line", key_down); + if (key_beg) + dobindkey(fundamental_map, "beginning-of-line", key_beg); + else if (key_home) + dobindkey(fundamental_map, "beginning-of-line", key_home); + if (key_end) + dobindkey(fundamental_map, "end-of-line", key_end); + if (key_npage) + dobindkey(fundamental_map, "scroll-up", key_npage); + if (key_ppage) + dobindkey(fundamental_map, "scroll-down", key_ppage); + if (key_dc) + dobindkey(fundamental_map, "delete-char", key_dc); +#endif /* FKEYS */ + + if ((cp = getenv("TERM"))) { + if (((cp = startupfile(cp)) != NULL) && (load(cp) != TRUE)) + ewprintf("Error reading key initialization file"); + } + if (keypad_xmit) + /* turn on keypad */ + putpad(keypad_xmit, 1); +} + +/* + * Clean up the keyboard -- called by tttidy() + */ +void +ttykeymaptidy(void) +{ + if (keypad_local) + /* turn off keypad */ + putpad(keypad_local, 1); +} + +#else + +void +ttykeymapinit(void) +{ +} + +#endif /* XKEYS */ diff --git a/mg/tutorial b/mg/tutorial new file mode 100644 index 0000000..f025f67 --- /dev/null +++ b/mg/tutorial @@ -0,0 +1,349 @@ +The mg Tutorial +--------------- + +The mg editor is a public domain editor intended to loosely resemble GNU Emacs, +while still retaining fast speed and a small memory footprint. + +Most mg commands involve using the Control (sometimes labelled "Ctrl") or the +Meta (sometimes labelled "Alt") key. We will use the following conventions in +this tutorial: + + C- means hold down the Control key while typing the character . + M- means hold down the Meta key while typing the character . + +If you don't have a Meta key, you can use Esc instead. Press and release the +Esc key and type . This is equivalent to M-. + +The first thing to learn is how to move up and down a document. To move your +cursor down, use the down-arrow cursor key or C-n (Control and n). + +>> Now type C-n multiple times and move your cursor past this line. + +Congratulations. You have now learned how to move your cursor down. Note how +mg has redrawn your screen so that the cursor is now in the middle of the +screen. This is a feature of mg, which allows you to see the lines before and +after the current cursor position. + +To move your cursor up, you can use the up-arrow cursor key or C-p (Control and +p). + +>> Try using C-p and C-n to move up and down and then move past this line. + +The next commands to learn are how to move your cursor left and right. To do +this, you can use the left-arrow and right-arrow cursor keys. Alternatively, +you can use C-b and C-f to do this. + +>> Practise using the arrow keys or C-b and C-f on this line. + +To make it easy to remember these commands, it helps to remember by letter: +P - Previous line, N - Next line, B - Backwards and F - Forward. + +Now that you've learned how to move single characters at a time, next we learn +how to move one word at a time. To do this, you can use M-f (Meta and f) or +M-b (Meta and b) to move forwards and backwards, one word at a time. + +>> Try moving one word at a time by using M-f and M-b on this line. + +Notice how the Ctrl and Meta key combinations perform related functions. C-f +moves one letter forward, whereas M-f moves one word forward. There are many +key combinations in mg, where C- will perform one function and M- +will perform a similar related function. + +Next, we will learn how to refresh and redraw the screen. + +>> Now move the cursor down to this line and then type C-l (that's Control and + lowercase L) to refresh the screen. + +Note that C-l refreshes the screen and centers it on the line you typed it on. + +To move to the beginning or end of a line, you can use the Home and End keys, +or you can use C-a and C-e to move to the beginning and end. + +>> Use C-a and C-e to move to the beginning and end of this line. + +The next commands we will learn is how to move up and down, one page at a time. +To do this, you can use the Page Up (sometimes labelled PgUp) and Page Down +(sometimes labelled PgDn) keys. You can also use C-v and M-v to do this. C-v +moves the cursor down one page and M-v moves it up one page. + +>> Try using M-v and C-v to move up and down, one page at a time. + +The final two motion commands we will learn are M-< (Meta-Less than) and +M-> (Meta-Greater than) which move you to the beginning and end of a file, +respectively. You may not want to try that now as you will probably lose your +place in this tutorial. Note that on most terminals, < is above the , key, so +you'll need to press the Shift key to type <. + +Movement Summary +----------------- + +The following is a summary of the movement commands we've learned so far: + + C-f Move forward one character (can also use right arrow key). + C-b Move backward one character (can also use left arrow key). + C-p Move up one line (can also use up arrow key). + C-n Move down one line (can also use down arrow key). + M-f Move forward one word. + M-b Move backward one word. + C-a Move to beginning of line (can also use Home key). + C-e Move to end of line (can also use End key). + C-v Move forward one page (can also use PgDn/Page Down key). + M-v Move backward one page (can also use PgUp/Page Up key). + M-< Move to beginning of file. + M-> Move to end of file. + +Now that you've mastered the basics of moving around in mg, you can cause mg +to execute these commands multiple times. The way to do this is to type C-u +followed by some digits followed by a movement command. + +>> Type C-u 5 C-f to move forward 5 characters. + +In general, C-u allows you to execute any command multiple times, not just +cursor motion commands. The only exception to this rule are C-v and M-v. +When using these two commands with an argument, they move the cursor by that +many lines instead of pages. + +Cancelling mg Commands +---------------------- + +If you have started typing out a command that you didn't mean to finish, you +can use the C-g command to cancel the command immediately. + +>> For example, type C-u 50 and then type C-g to cancel the C-u command. +>> Type Esc and then C-g to cancel the Esc key. + +In general, you can use C-g to stop any mg commands. You may type it multiple +times if you wish. You should see the word "Quit" appear in the bottom of the +screen when you type C-g indicating that a command was cancelled. + +In general, when in doubt, use C-g to get out of trouble. + + +Inserting/Deleting Text +----------------------- + +To insert text anywhere, simply move your cursor to the appropriate position +and begin typing. To delete characters, use the backspace key. If you use +M- (Meta and backspace key), you will delete one word instead +of one character at a time. + +To delete characters to the right of the cursor, you can use C-d to delete +characters to the right of the current position. If you use M-d instead of +C-d, you can delete one word at a time instead of one character at a time. + +>> Try inserting and deleting characters and words on this line. + +Note that if you type too many characters on a single line, the line will +scroll off the screen and you will see a $ on the line to indicate that the +line is too long to fit on the screen at one time. + +To delete a line at a time, you can use C-k to kill the line from the current +cursor position to the end of the line. You can type C-k multiple times to +kill many lines. + +You can issue insert or delete commands multiple times using C-u. For example, +C-u 10 e will type out eeeeeeeeee, C-u 4 M-d will delete four words to the +right of the cursor and so on. + +To undo any operation, you can use C-_ (That's control-underscore). + +Now if you kill something that you didn't mean to, you can yank it back from +the dead by using C-y. In general, when you kill something bigger than a single +character, mg saves it in a buffer somewhere and you can restore it by using +C-y. This is useful for moving text around. You can kill text in one place, +move your cursor to the new location and then use C-y to paste it there. + +Search for Text +--------------- + +To search for text, type C-s followed by the text you wish to search for. Note +that as you start typing the characters, mg automatically searches as you type +the characters. + +To continue searching the text you're looking for, type C-s to find the next +instance. To search in reverse, type C-r instead of C-s. If you type C-s or +C-r twice, it will simply search for the last text that you searched for. + +To stop searching for text, simply use the cursor keys (or C-f, C-b etc.) or +C-g to stop the search operation. + +>> Use C-s foo to search for "foo" in the text. You can use C-s again to + find other instances of foo in the file. + +Note that if a word cannot be found, it will say Failing I-search: at the +bottom of the screen. Typing C-s again will wrap the search around from the +top of the file and begin searching from there. + +Replace Text +------------ + +To replace text, use M-%. You will be prompted for the text to search for and +the text to replace it with. You will then be taken to the first instance of +text from the current position. At this point you can do one of the following: + + y - Replace the text at this instance and search for more items. + n - Skip this instance and search for more items. + . or Enter - Stop replacing text (You can also use C-g). + ! - Replace all the instances without prompting at each one. + +>> Try replacing "frobnitz" with "zutwalt" on this line. + +Cut/Copy/Paste Text +------------------- + +As explained above, you can cut regions using C-k to kill multiple lines. To +paste the text that you just cut, simply move your cursor to the point and +then type C-y to restore the text. You may type C-y multiple times to restore +the text. Hence, to copy text, you can use C-k to kill all the lines, use C-y +to restore it immediately, then move to the region you want to copy it to and +then type C-y again to restore the last cut text block again. + +Another way to cut or copy chunks of text is to first position your cursor at +the starting point of the chunk of text. Then type C- to mark this as +the starting point to cut or copy. Then move the cursor to the end point of the +text chunk that you wish to manipulate. Then type C-w to cut the region, or +M-w to copy the region. If you wish to cancel marking a block of text, simply +type C-g to cancel the operation. + +To paste the region that you've cut or copied above, simply move your cursor +to the desired location and then type C-y to paste it. + +Status Line +----------- + +At the bottom of your screen is a reverse highlighted line. This is the status +line and lets you know some useful information about the file you're editing. + +On the status line, you should see "Mg: tutorial". This lets you know that +you're editing a file named "tutorial". If you've edited this file and not +saved it, it should have a "**" to the left of those words. If this file is +read-only, you should see a "%%" to the left of those words. + +To the right of the status line, you should see L followed by digits and C +followed by some more digits. These indicate the line number and column number +of the file that your cursor is currently on. If you move the cursor around, +you should see the line and column number change. + +In the middle of the screen, you should see the word "(fundamental)" which +indicates that the current editing mode is "fundamental-mode". The mg editor +also supports a c-mode that is more suited to editing C code. There are also +some other useful editing modes for different situations. See the man page +for mg(1) to learn about the various editing modes. + +Opening and Saving Files +------------------------ + +To open a file, you can use C-x C-f. You will then be prompted for a file name. +If you type a file name that doesn't already exist, a new file will be opened +for you. If the file name already exists, then it will be opened for you and +you can begin editing it. Note that you do not need to type the whole file +name for an existing file. You can type part of the file name and then press +the TAB key. If there is only file name that matches, mg will fill in the rest +of the file name for you. If there are multiple files, mg will display that +the choice is ambiguous. If you type the TAB key again, mg will show you all +the available choices for file names. + +NOTE: If you type C-x f instead of C-x C-f, you can use C-g to cancel the +Set-Fill-Column command. You can also use C-g to cancel the C-x C-f command +if you don't wish to open a new file. + +To save the file once you've edited it, use C-x C-s to save the file. When +mg is done saving the file, you should see the words "Wrote /path/to/file" +in the bottom of your screen. In general, it is a good idea to save quite +often. When you save a file, mg saves a backup of the file with a tilde (~) +character at the end. + +Windows +------- + +The mg editor can support several windows at the same time, each one displaying +different text. To split a screen into two horizontal windows use C-x 2 to do +this. To return to one window, use C-x 1 to close the other windows and only +keep the current window. + +>> Use C-x 2 to split the screen into two windows. + +>> Use C-x o to move from one window to the other. You can scroll up and down + in each window using the cursor keys or C-n and C-p keys. + +>> Use C-x 1 to restore back to one window. + +Buffers +------- + +The mg editor is capable of editing multiple files at the same time. When you +open a second file with C-x C-f, the first file is still being edited by mg. +You can list all the buffers that are opened by mg by typing C-x C-b. The +screen should divide into two and the top window will list the buffers that +are currently open. Use C-x o to switch to the top window (we already learned +this key combination above in the Windows section) and then use the arrow keys +to move to the buffer you wish to switch to and then type the Enter key to +select that buffer. Then use C-x 1 to switch back to only one window. + +You may also move back to the last opened buffer by using C-x b to toggle back +and forth between two buffers. Note the difference between C-x b and C-x C-b. + +>> Use C-x C-f to open a new file +>> Use C-x b to switch back and forth between that buffer and this one. + +To edit files in multiple windows, use C-x 2 to split the screen into two +windows. Then use C-x C-f to open a new file in one of the two windows. You +can then switch between the two windows using C-x o. You can switch between +buffers in any window using C-x b. To go back to one window, use C-x 1. + +To kill any buffer, use C-x k. You will be prompted for the buffer to kill. +By default, the current buffer is selected as the one to kill. You may also +type another buffer name or use C-g to cancel the operation. + +Extended Commands +----------------- + +The mg editor has several extended commands, more than what can be covered +by the Control and Meta keys. The mg editor gets around this by using what is +called the X (eXtend) command. There are two forms of this: + + C-x Character eXtension. Followed by one character. + M-x Named character eXtension. Followed by a long command. + +You've already seen C-x C-f and C-x C-s to open and save a file. There are +other longer commands. For instance, you can also open a file by typing +M-x open-file Enter. When you type a command using M-x, mg prompts you for +the command at the bottom of the screen. You can type out the whole command +if you wish, or you can type out part of the command and then use the TAB key +for autocompleting the command. + +For instance, to replace text, you can type M-x repl TAB enter to execute +the replace-text command. To cancel this command, type C-g. + +To see a list of all available mg(1) commands, consult the man page. + +Exiting mg +---------- + +To exit mg temporarily and return to the shell, use C-z. This will take you +back to the command shell. To return back to mg, type fg in the shell and you +will be returned to your mg session. + +To exit mg permanently, type C-x C-c. If you have any unsaved buffers, you +will be asked if you wish to save them or not. + +Conclusion +---------- + +This tutorial is meant to get new users up and running with mg. There is more +information available via the mg(1) man page. If you have any suggestions for +improvement, please don't hesitate to drop a message or (better still) submit +a diff to tech@openbsd.org. + +Author Info +----------- + +Original Author of this document: Mayukh Bose, +Date last updated: 2012-05-25 + +Copyright +--------- + +None. This document is in the public domain. + + diff --git a/mg/undo.c b/mg/undo.c new file mode 100644 index 0000000..1efd2a1 --- /dev/null +++ b/mg/undo.c @@ -0,0 +1,585 @@ +/* $OpenBSD: undo.c,v 1.50 2010/06/30 19:12:54 oga Exp $ */ +/* + * This file is in the public domain + */ + +#include "def.h" +#include "kbd.h" + +#define MAX_FREE_RECORDS 32 + +/* + * Local variables + */ +static struct undoq undo_free; +static int undo_free_num; +static int boundary_flag = TRUE; +static int undo_enable_flag = TRUE; + +/* + * Local functions + */ +static int find_dot(struct line *, int); +static int find_lo(int, struct line **, int *, int *); +static struct undo_rec *new_undo_record(void); +static int drop_oldest_undo_record(void); + +/* + * find_dot, find_lo() + * + * Find an absolute dot in the buffer from a line/offset pair, and vice-versa. + * + * Since lines can be deleted while they are referenced by undo record, we + * need to have an absolute dot to have something reliable. + */ +static int +find_dot(struct line *lp, int off) +{ + int count = 0; + struct line *p; + + for (p = curbp->b_headp; p != lp; p = lforw(p)) { + if (count != 0) { + if (p == curbp->b_headp) { + ewprintf("Error: Undo stuff called with a" + "nonexistent line"); + return (FALSE); + } + } + count += llength(p) + 1; + } + count += off; + + return (count); +} + +static int +find_lo(int pos, struct line **olp, int *offset, int *lnum) +{ + struct line *p; + int lineno; + + p = curbp->b_headp; + lineno = 0; + while (pos > llength(p)) { + pos -= llength(p) + 1; + if ((p = lforw(p)) == curbp->b_headp) { + *olp = NULL; + *offset = 0; + return (FALSE); + } + lineno++; + } + *olp = p; + *offset = pos; + *lnum = lineno; + + return (TRUE); +} + +static struct undo_rec * +new_undo_record(void) +{ + struct undo_rec *rec; + + rec = TAILQ_FIRST(&undo_free); + if (rec != NULL) { + /* Remove it from the free-list */ + TAILQ_REMOVE(&undo_free, rec, next); + undo_free_num--; + } else { + if ((rec = malloc(sizeof(*rec))) == NULL) + panic("Out of memory in undo code (record)"); + } + memset(rec, 0, sizeof(struct undo_rec)); + + return (rec); +} + +void +free_undo_record(struct undo_rec *rec) +{ + static int initialised = 0; + + /* + * On the first run, do initialisation of the free list. + */ + if (initialised == 0) { + TAILQ_INIT(&undo_free); + initialised = 1; + } + if (rec->content != NULL) { + free(rec->content); + rec->content = NULL; + } + if (undo_free_num >= MAX_FREE_RECORDS) { + free(rec); + return; + } + undo_free_num++; + + TAILQ_INSERT_HEAD(&undo_free, rec, next); +} + +/* + * Drop the oldest undo record in our list. Return 1 if we could remove it, + * 0 if the undo list was empty. + */ +static int +drop_oldest_undo_record(void) +{ + struct undo_rec *rec; + + rec = TAILQ_LAST(&curbp->b_undo, undoq); + if (rec != NULL) { + undo_free_num--; + TAILQ_REMOVE(&curbp->b_undo, rec, next); + free_undo_record(rec); + return (1); + } + return (0); +} + +static int +lastrectype(void) +{ + struct undo_rec *rec; + + if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL) + return (rec->type); + return (0); +} + +/* + * Returns TRUE if undo is enabled, FALSE otherwise. + */ +int +undo_enabled(void) +{ + return (undo_enable_flag); +} + +/* + * undo_enable: toggle undo_enable. + * Returns the previous value of the flag. + */ +int +undo_enable(int f, int n) +{ + int pon = undo_enable_flag; + + if (f & (FFARG | FFRAND)) + undo_enable_flag = n > 0; + else + undo_enable_flag = !undo_enable_flag; + + if (!(f & FFRAND)) + ewprintf("Undo %sabled", undo_enable_flag ? "en" : "dis"); + + return (pon); +} + +/* + * If undo is enabled, then: + * Toggle undo boundary recording. + * If called with an argument, (n > 0) => enable. Otherwise disable. + * In either case, add an undo boundary + * If undo is disabled, this function has no effect. + */ +int +undo_boundary_enable(int f, int n) +{ + int bon = boundary_flag; + + if (!undo_enable_flag) + return (FALSE); + + undo_add_boundary(FFRAND, 1); + + if (f & (FFARG | FFRAND)) + boundary_flag = n > 0; + else + boundary_flag = !boundary_flag; + + if (!(f & FFRAND)) + ewprintf("Undo boundaries %sabled", + boundary_flag ? "en" : "dis"); + + return (bon); +} + +/* + * Record an undo boundary, unless boundary_flag == FALSE. + * Does nothing if previous undo entry is already a boundary or 'modified' flag. + */ +int +undo_add_boundary(int f, int n) +{ + struct undo_rec *rec; + int last; + + if (boundary_flag == FALSE) + return (FALSE); + + last = lastrectype(); + if (last == BOUNDARY || last == MODIFIED) + return (TRUE); + + rec = new_undo_record(); + rec->type = BOUNDARY; + + TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); + + return (TRUE); +} + +/* + * Record an undo "modified" boundary + */ +void +undo_add_modified(void) +{ + struct undo_rec *rec; + + rec = new_undo_record(); + rec->type = MODIFIED; + + TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); + + return; +} + +int +undo_add_insert(struct line *lp, int offset, int size) +{ + struct region reg; + struct undo_rec *rec; + int pos; + + if (!undo_enable_flag) + return (TRUE); + reg.r_linep = lp; + reg.r_offset = offset; + reg.r_size = size; + + pos = find_dot(lp, offset); + + /* + * We try to reuse the last undo record to `compress' things. + */ + rec = TAILQ_FIRST(&curbp->b_undo); + if (rec != NULL && rec->type == INSERT) { + if (rec->pos + rec->region.r_size == pos) { + rec->region.r_size += reg.r_size; + return (TRUE); + } + } + + /* + * We couldn't reuse the last undo record, so prepare a new one. + */ + rec = new_undo_record(); + rec->pos = pos; + rec->type = INSERT; + memmove(&rec->region, ®, sizeof(struct region)); + rec->content = NULL; + + undo_add_boundary(FFRAND, 1); + + TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); + + return (TRUE); +} + +/* + * This of course must be done _before_ the actual deletion is done. + */ +int +undo_add_delete(struct line *lp, int offset, int size, int isreg) +{ + struct region reg; + struct undo_rec *rec; + int pos; + + if (!undo_enable_flag) + return (TRUE); + + reg.r_linep = lp; + reg.r_offset = offset; + reg.r_size = size; + + pos = find_dot(lp, offset); + + if (offset == llength(lp)) /* if it's a newline... */ + undo_add_boundary(FFRAND, 1); + else if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL) { + /* + * Separate this command from the previous one if we're not + * just before the previous record... + */ + if (!isreg && rec->type == DELETE) { + if (rec->pos - rec->region.r_size != pos) + undo_add_boundary(FFRAND, 1); + } + } + rec = new_undo_record(); + rec->pos = pos; + if (isreg) + rec->type = DELREG; + else + rec->type = DELETE; + memmove(&rec->region, ®, sizeof(struct region)); + do { + rec->content = malloc(reg.r_size + 1); + } while ((rec->content == NULL) && drop_oldest_undo_record()); + + if (rec->content == NULL) + panic("Out of memory"); + + region_get_data(®, rec->content, reg.r_size); + + if (isreg || lastrectype() != DELETE) + undo_add_boundary(FFRAND, 1); + + TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); + + return (TRUE); +} + +/* + * This of course must be called before the change takes place. + */ +int +undo_add_change(struct line *lp, int offset, int size) +{ + if (!undo_enable_flag) + return (TRUE); + undo_add_boundary(FFRAND, 1); + boundary_flag = FALSE; + undo_add_delete(lp, offset, size, 0); + undo_add_insert(lp, offset, size); + boundary_flag = TRUE; + undo_add_boundary(FFRAND, 1); + + return (TRUE); +} + +/* + * Show the undo records for the current buffer in a new buffer. + */ +/* ARGSUSED */ +int +undo_dump(int f, int n) +{ + struct undo_rec *rec; + struct buffer *bp; + struct mgwin *wp; + char buf[4096], tmp[1024]; + int num; + + /* + * Prepare the buffer for insertion. + */ + if ((bp = bfind("*undo*", TRUE)) == NULL) + return (FALSE); + bp->b_flag |= BFREADONLY; + bclear(bp); + popbuf(bp, WNONE); + + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_bufp == bp) { + wp->w_dotp = bp->b_headp; + wp->w_doto = 0; + } + } + + num = 0; + TAILQ_FOREACH(rec, &curbp->b_undo, next) { + num++; + snprintf(buf, sizeof(buf), + "%d:\t %s at %d ", num, + (rec->type == DELETE) ? "DELETE": + (rec->type == DELREG) ? "DELREGION": + (rec->type == INSERT) ? "INSERT": + (rec->type == BOUNDARY) ? "----" : + (rec->type == MODIFIED) ? "MODIFIED": "UNKNOWN", + rec->pos); + + if (rec->content) { + (void)strlcat(buf, "\"", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%.*s", rec->region.r_size, + rec->content); + (void)strlcat(buf, tmp, sizeof(buf)); + (void)strlcat(buf, "\"", sizeof(buf)); + } + snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size); + if (strlcat(buf, tmp, sizeof(buf)) >= sizeof(buf)) { + ewprintf("Undo record too large. Aborted."); + return (FALSE); + } + addlinef(bp, "%s", buf); + } + return (TRUE); +} + +/* + * After the user did action1, then action2, then action3: + * + * [action3] <--- Undoptr + * [action2] + * [action1] + * ------ + * [undo] + * + * After undo: + * + * [undo of action3] + * [action2] <--- Undoptr + * [action1] + * ------ + * [undo] + * + * After another undo: + * + * + * [undo of action2] + * [undo of action3] + * [action1] <--- Undoptr + * ------ + * [undo] + * + * Note that the "undo of actionX" have no special meaning. Only when + * we undo a deletion, the insertion will be recorded just as if it + * was typed on the keyboard. Resulting in the inverse operation being + * saved in the list. + * + * If undoptr reaches the bottom of the list, or if we moved between + * two undo actions, we make it point back at the topmost record. This is + * how we handle redoing. + */ +/* ARGSUSED */ +int +undo(int f, int n) +{ + struct undo_rec *ptr, *nptr; + int done, rval; + struct line *lp; + int offset, save, dot; + static int nulled = FALSE; + int lineno; + + if (n < 0) + return (FALSE); + + dot = find_dot(curwp->w_dotp, curwp->w_doto); + + ptr = curbp->b_undoptr; + + /* first invocation, make ptr point back to the top of the list */ + if ((ptr == NULL && nulled == TRUE) || rptcount == 0) { + ptr = TAILQ_FIRST(&curbp->b_undo); + nulled = TRUE; + } + + rval = TRUE; + while (n--) { + /* if we have a spurious boundary, free it and move on.... */ + while (ptr && ptr->type == BOUNDARY) { + nptr = TAILQ_NEXT(ptr, next); + TAILQ_REMOVE(&curbp->b_undo, ptr, next); + free_undo_record(ptr); + ptr = nptr; + } + /* + * Ptr is NULL, but on the next run, it will point to the + * top again, redoing all stuff done in the buffer since + * its creation. + */ + if (ptr == NULL) { + ewprintf("No further undo information"); + rval = FALSE; + nulled = TRUE; + break; + } + nulled = FALSE; + + /* + * Loop while we don't get a boundary specifying we've + * finished the current action... + */ + + undo_add_boundary(FFRAND, 1); + + save = boundary_flag; + boundary_flag = FALSE; + + done = 0; + do { + /* + * Move to where this has to apply + * + * Boundaries (and the modified flag) are put as + * position 0 (to save lookup time in find_dot) + * so we must not move there... + */ + if (ptr->type != BOUNDARY && ptr->type != MODIFIED) { + if (find_lo(ptr->pos, &lp, + &offset, &lineno) == FALSE) { + ewprintf("Internal error in Undo!"); + rval = FALSE; + break; + } + curwp->w_dotp = lp; + curwp->w_doto = offset; + curwp->w_markline = curwp->w_dotline; + curwp->w_dotline = lineno; + } + + /* + * Do operation^-1 + */ + switch (ptr->type) { + case INSERT: + ldelete(ptr->region.r_size, KNONE); + break; + case DELETE: + lp = curwp->w_dotp; + offset = curwp->w_doto; + region_put_data(ptr->content, + ptr->region.r_size); + curwp->w_dotp = lp; + curwp->w_doto = offset; + break; + case DELREG: + region_put_data(ptr->content, + ptr->region.r_size); + break; + case BOUNDARY: + done = 1; + break; + case MODIFIED: + curbp->b_flag &= ~BFCHG; + break; + default: + break; + } + + /* And move to next record */ + ptr = TAILQ_NEXT(ptr, next); + } while (ptr != NULL && !done); + + boundary_flag = save; + undo_add_boundary(FFRAND, 1); + + ewprintf("Undo!"); + } + /* + * Record where we are. (we have to save our new position at the end + * since we change the dot when undoing....) + */ + curbp->b_undoptr = ptr; + + curbp->b_undopos = find_dot(curwp->w_dotp, curwp->w_doto); + + return (rval); +} diff --git a/mg/version.c b/mg/version.c new file mode 100644 index 0000000..2dc08cd --- /dev/null +++ b/mg/version.c @@ -0,0 +1,24 @@ +/* $OpenBSD: version.c,v 1.9 2005/06/14 18:14:40 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * This file contains the string that gets written + * out by the emacs-version command. + */ + +#include "def.h" + +const char version[] = "Mg 2a"; + +/* + * Display the version. All this does + * is copy the version string onto the echo line. + */ +/* ARGSUSED */ +int +showversion(int f, int n) +{ + ewprintf("%s", version); + return (TRUE); +} diff --git a/mg/window.c b/mg/window.c new file mode 100644 index 0000000..db913c9 --- /dev/null +++ b/mg/window.c @@ -0,0 +1,428 @@ +/* $OpenBSD: window.c,v 1.28 2011/08/01 12:15:23 lum Exp $ */ + +/* This file is in the public domain. */ + +/* + * Window handling. + */ + +#include "def.h" + +struct mgwin * +new_window(struct buffer *bp) +{ + struct mgwin *wp; + + wp = calloc(1, sizeof(struct mgwin)); + if (wp == NULL) + return (NULL); + + wp->w_bufp = bp; + wp->w_dotp = NULL; + wp->w_doto = 0; + wp->w_markp = NULL; + wp->w_marko = 0; + wp->w_rflag = 0; + wp->w_frame = 0; + wp->w_wrapline = NULL; + wp->w_dotline = wp->w_markline = 1; + if (bp) + bp->b_nwnd++; + return (wp); +} + +/* + * Reposition dot in the current window to line "n". If the argument is + * positive, it is that line. If it is negative it is that line from the + * bottom. If it is 0 the window is centered (this is what the standard + * redisplay code does). If GOSREC is undefined, default is 0, so it acts + * like GNU. If GOSREC is defined, with no argument it defaults to 1 and + * works like in Gosling. + */ +/* ARGSUSED */ +int +reposition(int f, int n) +{ +#ifndef GOSREC + curwp->w_frame = (f & FFARG) ? (n >= 0 ? n + 1 : n) : 0; +#else /* !GOSREC */ + curwp->w_frame = n; +#endif /* !GOSREC */ + curwp->w_rflag |= WFFRAME; + sgarbf = TRUE; + return (TRUE); +} + +/* + * Refresh the display. A call is made to the "ttresize" entry in the + * terminal handler, which tries to reset "nrow" and "ncol". They will, + * however, never be set outside of the NROW or NCOL range. If the display + * changed size, arrange that everything is redone, then call "update" to + * fix the display. We do this so the new size can be displayed. In the + * normal case the call to "update" in "main.c" refreshes the screen, and + * all of the windows need not be recomputed. This call includes a + * 'force' parameter to ensure that the redraw is done, even after a + * a suspend/continue (where the window size parameters will already + * be updated). Note that when you get to the "display unusable" + * message, the screen will be messed up. If you make the window bigger + * again, and send another command, everything will get fixed! + */ +int +redraw(int f, int n) +{ + return (do_redraw(f, n, FALSE)); +} + +/* ARGSUSED */ +int +do_redraw(int f, int n, int force) +{ + struct mgwin *wp; + int oldnrow, oldncol; + + oldnrow = nrow; + oldncol = ncol; + ttresize(); + if (nrow != oldnrow || ncol != oldncol || force) { + + /* find last */ + wp = wheadp; + while (wp->w_wndp != NULL) + wp = wp->w_wndp; + + /* check if too small */ + if (nrow < wp->w_toprow + 3) { + ewprintf("Display unusable"); + return (FALSE); + } + wp->w_ntrows = nrow - wp->w_toprow - 2; + sgarbf = TRUE; + update(); + } else + sgarbf = TRUE; + return (TRUE); +} + +/* + * The command to make the next window (next => down the screen) the current + * window. There are no real errors, although the command does nothing if + * there is only 1 window on the screen. + */ +/* ARGSUSED */ +int +nextwind(int f, int n) +{ + struct mgwin *wp; + + if ((wp = curwp->w_wndp) == NULL) + wp = wheadp; + curwp = wp; + curbp = wp->w_bufp; + return (TRUE); +} + +/* not in GNU Emacs */ +/* + * This command makes the previous window (previous => up the screen) the + * current window. There are no errors, although the command does not do + * a lot if there is only 1 window. + */ +/* ARGSUSED */ +int +prevwind(int f, int n) +{ + struct mgwin *wp1, *wp2; + + wp1 = wheadp; + wp2 = curwp; + if (wp1 == wp2) + wp2 = NULL; + while (wp1->w_wndp != wp2) + wp1 = wp1->w_wndp; + curwp = wp1; + curbp = wp1->w_bufp; + return (TRUE); +} + +/* + * This command makes the current window the only window on the screen. Try + * to set the framing so that "." does not have to move on the display. Some + * care has to be taken to keep the values of dot and mark in the buffer + * structures right if the destruction of a window makes a buffer become + * undisplayed. + */ +/* ARGSUSED */ +int +onlywind(int f, int n) +{ + struct mgwin *wp; + struct line *lp; + int i; + + while (wheadp != curwp) { + wp = wheadp; + wheadp = wp->w_wndp; + if (--wp->w_bufp->b_nwnd == 0) { + wp->w_bufp->b_dotp = wp->w_dotp; + wp->w_bufp->b_doto = wp->w_doto; + wp->w_bufp->b_markp = wp->w_markp; + wp->w_bufp->b_marko = wp->w_marko; + } + free(wp); + } + while (curwp->w_wndp != NULL) { + wp = curwp->w_wndp; + curwp->w_wndp = wp->w_wndp; + if (--wp->w_bufp->b_nwnd == 0) { + wp->w_bufp->b_dotp = wp->w_dotp; + wp->w_bufp->b_doto = wp->w_doto; + wp->w_bufp->b_markp = wp->w_markp; + wp->w_bufp->b_marko = wp->w_marko; + } + free(wp); + } + lp = curwp->w_linep; + i = curwp->w_toprow; + while (i != 0 && lback(lp) != curbp->b_headp) { + --i; + lp = lback(lp); + } + curwp->w_toprow = 0; + + /* 2 = mode, echo */ + curwp->w_ntrows = nrow - 2; + curwp->w_linep = lp; + curwp->w_rflag |= WFMODE | WFFULL; + return (TRUE); +} + +/* + * Split the current window. A window smaller than 3 lines cannot be split. + * The only other error that is possible is a "malloc" failure allocating the + * structure for the new window. + * If called with a FFOTHARG, flags on the new window are set to 'n'. + */ +/* ARGSUSED */ +int +splitwind(int f, int n) +{ + struct mgwin *wp, *wp1, *wp2; + struct line *lp; + int ntru, ntrd, ntrl; + + if (curwp->w_ntrows < 3) { + ewprintf("Cannot split a %d line window", curwp->w_ntrows); + return (FALSE); + } + wp = new_window(curbp); + if (wp == NULL) { + ewprintf("Unable to create a window"); + return (FALSE); + } + + /* use the current dot and mark */ + wp->w_dotp = curwp->w_dotp; + wp->w_doto = curwp->w_doto; + wp->w_markp = curwp->w_markp; + wp->w_marko = curwp->w_marko; + wp->w_dotline = curwp->w_dotline; + wp->w_markline = curwp->w_markline; + + /* figure out which half of the screen we're in */ + ntru = (curwp->w_ntrows - 1) / 2; /* Upper size */ + ntrl = (curwp->w_ntrows - 1) - ntru; /* Lower size */ + + for (lp = curwp->w_linep, ntrd = 0; lp != curwp->w_dotp; + lp = lforw(lp)) + ntrd++; + + lp = curwp->w_linep; + + /* old is upper window */ + if (ntrd <= ntru) { + /* hit mode line */ + if (ntrd == ntru) + lp = lforw(lp); + curwp->w_ntrows = ntru; + wp->w_wndp = curwp->w_wndp; + curwp->w_wndp = wp; + wp->w_toprow = curwp->w_toprow + ntru + 1; + wp->w_ntrows = ntrl; + /* old is lower window */ + } else { + wp1 = NULL; + wp2 = wheadp; + while (wp2 != curwp) { + wp1 = wp2; + wp2 = wp2->w_wndp; + } + if (wp1 == NULL) + wheadp = wp; + else + wp1->w_wndp = wp; + wp->w_wndp = curwp; + wp->w_toprow = curwp->w_toprow; + wp->w_ntrows = ntru; + + /* mode line */ + ++ntru; + curwp->w_toprow += ntru; + curwp->w_ntrows = ntrl; + while (ntru--) + lp = lforw(lp); + } + + /* adjust the top lines if necessary */ + curwp->w_linep = lp; + wp->w_linep = lp; + + curwp->w_rflag |= WFMODE | WFFULL; + wp->w_rflag |= WFMODE | WFFULL; + /* if FFOTHARG, set flags) */ + if (f & FFOTHARG) + wp->w_flag = n; + + return (TRUE); +} + +/* + * Enlarge the current window. Find the window that loses space. Make sure + * it is big enough. If so, hack the window descriptions, and ask redisplay + * to do all the hard work. You don't just set "force reframe" because dot + * would move. + */ +/* ARGSUSED */ +int +enlargewind(int f, int n) +{ + struct mgwin *adjwp; + struct line *lp; + int i; + + if (n < 0) + return (shrinkwind(f, -n)); + if (wheadp->w_wndp == NULL) { + ewprintf("Only one window"); + return (FALSE); + } + if ((adjwp = curwp->w_wndp) == NULL) { + adjwp = wheadp; + while (adjwp->w_wndp != curwp) + adjwp = adjwp->w_wndp; + } + if (adjwp->w_ntrows <= n) { + ewprintf("Impossible change"); + return (FALSE); + } + + /* shrink below */ + if (curwp->w_wndp == adjwp) { + lp = adjwp->w_linep; + for (i = 0; i < n && lp != adjwp->w_bufp->b_headp; ++i) + lp = lforw(lp); + adjwp->w_linep = lp; + adjwp->w_toprow += n; + /* shrink above */ + } else { + lp = curwp->w_linep; + for (i = 0; i < n && lback(lp) != curbp->b_headp; ++i) + lp = lback(lp); + curwp->w_linep = lp; + curwp->w_toprow -= n; + } + curwp->w_ntrows += n; + adjwp->w_ntrows -= n; + curwp->w_rflag |= WFMODE | WFFULL; + adjwp->w_rflag |= WFMODE | WFFULL; + return (TRUE); +} + +/* + * Shrink the current window. Find the window that gains space. Hack at the + * window descriptions. Ask the redisplay to do all the hard work. + */ +int +shrinkwind(int f, int n) +{ + struct mgwin *adjwp; + struct line *lp; + int i; + + if (n < 0) + return (enlargewind(f, -n)); + if (wheadp->w_wndp == NULL) { + ewprintf("Only one window"); + return (FALSE); + } + /* + * Bit of flakiness - KRANDOM means it was an internal call, and + * to be trusted implicitly about sizes. + */ + if (!(f & FFRAND) && curwp->w_ntrows <= n) { + ewprintf("Impossible change"); + return (FALSE); + } + if ((adjwp = curwp->w_wndp) == NULL) { + adjwp = wheadp; + while (adjwp->w_wndp != curwp) + adjwp = adjwp->w_wndp; + } + + /* grow below */ + if (curwp->w_wndp == adjwp) { + lp = adjwp->w_linep; + for (i = 0; i < n && lback(lp) != adjwp->w_bufp->b_headp; ++i) + lp = lback(lp); + adjwp->w_linep = lp; + adjwp->w_toprow -= n; + /* grow above */ + } else { + lp = curwp->w_linep; + for (i = 0; i < n && lp != curbp->b_headp; ++i) + lp = lforw(lp); + curwp->w_linep = lp; + curwp->w_toprow += n; + } + curwp->w_ntrows -= n; + adjwp->w_ntrows += n; + curwp->w_rflag |= WFMODE | WFFULL; + adjwp->w_rflag |= WFMODE | WFFULL; + return (TRUE); +} + +/* + * Delete current window. Call shrink-window to do the screen updating, then + * throw away the window. + */ +/* ARGSUSED */ +int +delwind(int f, int n) +{ + struct mgwin *wp, *nwp; + + wp = curwp; /* Cheap... */ + + /* shrinkwind returning false means only one window... */ + if (shrinkwind(FFRAND, wp->w_ntrows + 1) == FALSE) + return (FALSE); + if (--wp->w_bufp->b_nwnd == 0) { + wp->w_bufp->b_dotp = wp->w_dotp; + wp->w_bufp->b_doto = wp->w_doto; + wp->w_bufp->b_markp = wp->w_markp; + wp->w_bufp->b_marko = wp->w_marko; + } + + /* since shrinkwind did't crap out, we know we have a second window */ + if (wp == wheadp) + wheadp = curwp = wp->w_wndp; + else if ((curwp = wp->w_wndp) == NULL) + curwp = wheadp; + curbp = curwp->w_bufp; + for (nwp = wheadp; nwp != NULL; nwp = nwp->w_wndp) + if (nwp->w_wndp == wp) { + nwp->w_wndp = wp->w_wndp; + break; + } + free(wp); + return (TRUE); +} diff --git a/mg/word.c b/mg/word.c new file mode 100644 index 0000000..c18b620 --- /dev/null +++ b/mg/word.c @@ -0,0 +1,346 @@ +/* $OpenBSD: word.c,v 1.15 2008/09/15 16:11:35 kjell Exp $ */ + +/* This file is in the public domain. */ + +/* + * Word mode commands. + * The routines in this file implement commands that work word at a time. + * There are all sorts of word mode commands. + */ + +#include "def.h" + +RSIZE countfword(void); + +/* + * Move the cursor backward by "n" words. All of the details of motion are + * performed by the "backchar" and "forwchar" routines. + */ +/* ARGSUSED */ +int +backword(int f, int n) +{ + if (n < 0) + return (forwword(f | FFRAND, -n)); + if (backchar(FFRAND, 1) == FALSE) + return (FALSE); + while (n--) { + while (inword() == FALSE) { + if (backchar(FFRAND, 1) == FALSE) + return (TRUE); + } + while (inword() != FALSE) { + if (backchar(FFRAND, 1) == FALSE) + return (TRUE); + } + } + return (forwchar(FFRAND, 1)); +} + +/* + * Move the cursor forward by the specified number of words. All of the + * motion is done by "forwchar". + */ +/* ARGSUSED */ +int +forwword(int f, int n) +{ + if (n < 0) + return (backword(f | FFRAND, -n)); + while (n--) { + while (inword() == FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + return (TRUE); + } + while (inword() != FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + return (TRUE); + } + } + return (TRUE); +} + +/* + * Move the cursor forward by the specified number of words. As you move, + * convert any characters to upper case. + */ +/* ARGSUSED */ +int +upperword(int f, int n) +{ + int c, s; + RSIZE size; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read-only"); + return (FALSE); + } + + if (n < 0) + return (FALSE); + while (n--) { + while (inword() == FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + return (TRUE); + } + size = countfword(); + undo_add_change(curwp->w_dotp, curwp->w_doto, size); + + while (inword() != FALSE) { + c = lgetc(curwp->w_dotp, curwp->w_doto); + if (ISLOWER(c) != FALSE) { + c = TOUPPER(c); + lputc(curwp->w_dotp, curwp->w_doto, c); + lchange(WFFULL); + } + if (forwchar(FFRAND, 1) == FALSE) + return (TRUE); + } + } + return (TRUE); +} + +/* + * Move the cursor forward by the specified number of words. As you move + * convert characters to lower case. + */ +/* ARGSUSED */ +int +lowerword(int f, int n) +{ + int c, s; + RSIZE size; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read-only"); + return (FALSE); + } + if (n < 0) + return (FALSE); + while (n--) { + while (inword() == FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + return (TRUE); + } + size = countfword(); + undo_add_change(curwp->w_dotp, curwp->w_doto, size); + + while (inword() != FALSE) { + c = lgetc(curwp->w_dotp, curwp->w_doto); + if (ISUPPER(c) != FALSE) { + c = TOLOWER(c); + lputc(curwp->w_dotp, curwp->w_doto, c); + lchange(WFFULL); + } + if (forwchar(FFRAND, 1) == FALSE) + return (TRUE); + } + } + return (TRUE); +} + +/* + * Move the cursor forward by the specified number of words. As you move + * convert the first character of the word to upper case, and subsequent + * characters to lower case. Error if you try to move past the end of the + * buffer. + */ +/* ARGSUSED */ +int +capword(int f, int n) +{ + int c, s; + RSIZE size; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read-only"); + return (FALSE); + } + + if (n < 0) + return (FALSE); + while (n--) { + while (inword() == FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + return (TRUE); + } + size = countfword(); + undo_add_change(curwp->w_dotp, curwp->w_doto, size); + + if (inword() != FALSE) { + c = lgetc(curwp->w_dotp, curwp->w_doto); + if (ISLOWER(c) != FALSE) { + c = TOUPPER(c); + lputc(curwp->w_dotp, curwp->w_doto, c); + lchange(WFFULL); + } + if (forwchar(FFRAND, 1) == FALSE) + return (TRUE); + while (inword() != FALSE) { + c = lgetc(curwp->w_dotp, curwp->w_doto); + if (ISUPPER(c) != FALSE) { + c = TOLOWER(c); + lputc(curwp->w_dotp, curwp->w_doto, c); + lchange(WFFULL); + } + if (forwchar(FFRAND, 1) == FALSE) + return (TRUE); + } + } + } + return (TRUE); +} + +/* + * Count characters in word, from current position + */ +RSIZE +countfword() +{ + RSIZE size; + struct line *dotp; + int doto; + + dotp = curwp->w_dotp; + doto = curwp->w_doto; + size = 0; + + while (inword() != FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + /* hit the end of the buffer */ + goto out; + ++size; + } +out: + curwp->w_dotp = dotp; + curwp->w_doto = doto; + return (size); +} + + +/* + * Kill forward by "n" words. + */ +/* ARGSUSED */ +int +delfword(int f, int n) +{ + RSIZE size; + struct line *dotp; + int doto; + int s; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read-only"); + return (FALSE); + } + if (n < 0) + return (FALSE); + + /* purge kill buffer */ + if ((lastflag & CFKILL) == 0) + kdelete(); + + thisflag |= CFKILL; + dotp = curwp->w_dotp; + doto = curwp->w_doto; + size = 0; + + while (n--) { + while (inword() == FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + /* hit the end of the buffer */ + goto out; + ++size; + } + while (inword() != FALSE) { + if (forwchar(FFRAND, 1) == FALSE) + /* hit the end of the buffer */ + goto out; + ++size; + } + } +out: + curwp->w_dotp = dotp; + curwp->w_doto = doto; + return (ldelete(size, KFORW)); +} + +/* + * Kill backwards by "n" words. The rules for success and failure are now + * different, to prevent strange behavior at the start of the buffer. The + * command only fails if something goes wrong with the actual delete of the + * characters. It is successful even if no characters are deleted, or if you + * say delete 5 words, and there are only 4 words left. I considered making + * the first call to "backchar" special, but decided that that would just be + * weird. Normally this is bound to "M-Rubout" and to "M-Backspace". + */ +/* ARGSUSED */ +int +delbword(int f, int n) +{ + RSIZE size; + int s; + + if ((s = checkdirty(curbp)) != TRUE) + return (s); + if (curbp->b_flag & BFREADONLY) { + ewprintf("Buffer is read-only"); + return (FALSE); + } + + if (n < 0) + return (FALSE); + + /* purge kill buffer */ + if ((lastflag & CFKILL) == 0) + kdelete(); + thisflag |= CFKILL; + if (backchar(FFRAND, 1) == FALSE) + /* hit buffer start */ + return (TRUE); + + /* one deleted */ + size = 1; + while (n--) { + while (inword() == FALSE) { + if (backchar(FFRAND, 1) == FALSE) + /* hit buffer start */ + goto out; + ++size; + } + while (inword() != FALSE) { + if (backchar(FFRAND, 1) == FALSE) + /* hit buffer start */ + goto out; + ++size; + } + } + if (forwchar(FFRAND, 1) == FALSE) + return (FALSE); + + /* undo assumed delete */ + --size; +out: + return (ldelete(size, KBACK)); +} + +/* + * Return TRUE if the character at dot is a character that is considered to be + * part of a word. The word character list is hard coded. Should be settable. + */ +int +inword(void) +{ + /* can't use lgetc in ISWORD due to bug in OSK cpp */ + return (curwp->w_doto != llength(curwp->w_dotp) && + ISWORD(curwp->w_dotp->l_text[curwp->w_doto])); +} diff --git a/mg/yank.c b/mg/yank.c new file mode 100644 index 0000000..d408286 --- /dev/null +++ b/mg/yank.c @@ -0,0 +1,264 @@ +/* $OpenBSD: yank.c,v 1.10 2011/07/15 16:50:52 deraadt Exp $ */ + +/* This file is in the public domain. */ + +/* + * kill ring functions + */ + +#include "def.h" + +#include + +#ifndef KBLOCK +#define KBLOCK 256 /* Kill buffer block size. */ +#endif + +static char *kbufp = NULL; /* Kill buffer data. */ +static RSIZE kused = 0; /* # of bytes used in KB. */ +static RSIZE ksize = 0; /* # of bytes allocated in KB. */ +static RSIZE kstart = 0; /* # of first used byte in KB. */ + +static int kgrow(int); + +/* + * Delete all of the text saved in the kill buffer. Called by commands when + * a new kill context is created. The kill buffer array is released, just in + * case the buffer has grown to an immense size. No errors. + */ +void +kdelete(void) +{ + if (kbufp != NULL) { + free(kbufp); + kbufp = NULL; + kstart = kused = ksize = 0; + } +} + +/* + * Insert a character to the kill buffer, enlarging the buffer if there + * isn't any room. Always grow the buffer in chunks, on the assumption + * that if you put something in the kill buffer you are going to put more + * stuff there too later. Return TRUE if all is well, and FALSE on errors. + * Print a message on errors. Dir says whether to put it at back or front. + * This call is ignored if KNONE is set. + */ +int +kinsert(int c, int dir) +{ + if (dir == KNONE) + return (TRUE); + if (kused == ksize && dir == KFORW && kgrow(dir) == FALSE) + return (FALSE); + if (kstart == 0 && dir == KBACK && kgrow(dir) == FALSE) + return (FALSE); + if (dir == KFORW) + kbufp[kused++] = c; + else if (dir == KBACK) + kbufp[--kstart] = c; + else + panic("broken kinsert call"); /* Oh shit! */ + return (TRUE); +} + +/* + * kgrow - just get more kill buffer for the callee. If dir = KBACK + * we are trying to get space at the beginning of the kill buffer. + */ +static int +kgrow(int dir) +{ + int nstart; + char *nbufp; + + if ((unsigned)(ksize + KBLOCK) <= (unsigned)ksize) { + /* probably 16 bit unsigned */ + ewprintf("Kill buffer size at maximum"); + return (FALSE); + } + if ((nbufp = malloc((unsigned)(ksize + KBLOCK))) == NULL) { + ewprintf("Can't get %ld bytes", (long)(ksize + KBLOCK)); + return (FALSE); + } + nstart = (dir == KBACK) ? (kstart + KBLOCK) : (KBLOCK / 4); + bcopy(&(kbufp[kstart]), &(nbufp[nstart]), (int)(kused - kstart)); + if (kbufp != NULL) + free(kbufp); + kbufp = nbufp; + ksize += KBLOCK; + kused = kused - kstart + nstart; + kstart = nstart; + return (TRUE); +} + +/* + * This function gets characters from the kill buffer. If the character + * index "n" is off the end, it returns "-1". This lets the caller just + * scan along until it gets a "-1" back. + */ +int +kremove(int n) +{ + if (n < 0 || n + kstart >= kused) + return (-1); + return (CHARMASK(kbufp[n + kstart])); +} + +/* + * Copy a string into the kill buffer. kflag gives direction. + * if KNONE, do nothing. + */ +int +kchunk(char *cp1, RSIZE chunk, int kflag) +{ + /* + * HACK - doesn't matter, and fixes back-over-nl bug for empty + * kill buffers. + */ + if (kused == kstart) + kflag = KFORW; + + if (kflag & KFORW) { + while (ksize - kused < chunk) + if (kgrow(kflag) == FALSE) + return (FALSE); + bcopy(cp1, &(kbufp[kused]), (int)chunk); + kused += chunk; + } else if (kflag & KBACK) { + while (kstart < chunk) + if (kgrow(kflag) == FALSE) + return (FALSE); + bcopy(cp1, &(kbufp[kstart - chunk]), (int)chunk); + kstart -= chunk; + } + + return (TRUE); +} + +/* + * Kill line. If called without an argument, it kills from dot to the end + * of the line, unless it is at the end of the line, when it kills the + * newline. If called with an argument of 0, it kills from the start of the + * line to dot. If called with a positive argument, it kills from dot + * forward over that number of newlines. If called with a negative argument + * it kills any text before dot on the current line, then it kills back + * abs(arg) lines. + */ +/* ARGSUSED */ +int +killline(int f, int n) +{ + struct line *nextp; + RSIZE chunk; + int i, c; + + /* clear kill buffer if last wasn't a kill */ + if ((lastflag & CFKILL) == 0) + kdelete(); + thisflag |= CFKILL; + if (!(f & FFARG)) { + for (i = curwp->w_doto; i < llength(curwp->w_dotp); ++i) + if ((c = lgetc(curwp->w_dotp, i)) != ' ' && c != '\t') + break; + if (i == llength(curwp->w_dotp)) + chunk = llength(curwp->w_dotp) - curwp->w_doto + 1; + else { + chunk = llength(curwp->w_dotp) - curwp->w_doto; + if (chunk == 0) + chunk = 1; + } + } else if (n > 0) { + chunk = llength(curwp->w_dotp) - curwp->w_doto; + nextp = lforw(curwp->w_dotp); + if (nextp != curbp->b_headp) + chunk++; /* newline */ + if (nextp == curbp->b_headp) + goto done; /* EOL */ + i = n; + while (--i) { + chunk += llength(nextp); + nextp = lforw(nextp); + if (nextp != curbp->b_headp) + chunk++; /* newline */ + if (nextp == curbp->b_headp) + break; /* EOL */ + } + } else { + /* n <= 0 */ + chunk = curwp->w_doto; + curwp->w_doto = 0; + i = n; + while (i++) { + if (lforw(curwp->w_dotp)) + chunk++; + curwp->w_dotp = lback(curwp->w_dotp); + curwp->w_rflag |= WFMOVE; + chunk += llength(curwp->w_dotp); + } + } + /* + * KFORW here is a bug. Should be KBACK/KFORW, but we need to + * rewrite the ldelete code (later)? + */ +done: + if (chunk) + return (ldelete(chunk, KFORW)); + return (TRUE); +} + +/* + * Yank text back from the kill buffer. This is really easy. All of the work + * is done by the standard insert routines. All you do is run the loop, and + * check for errors. The blank lines are inserted with a call to "newline" + * instead of a call to "lnewline" so that the magic stuff that happens when + * you type a carriage return also happens when a carriage return is yanked + * back from the kill buffer. An attempt has been made to fix the cosmetic + * bug associated with a yank when dot is on the top line of the window + * (nothing moves, because all of the new text landed off screen). + */ +/* ARGSUSED */ +int +yank(int f, int n) +{ + struct line *lp; + int c, i, nline; + + if (n < 0) + return (FALSE); + + /* newline counting */ + nline = 0; + + undo_boundary_enable(FFRAND, 0); + while (n--) { + /* mark around last yank */ + isetmark(); + i = 0; + while ((c = kremove(i)) >= 0) { + if (c == '\n') { + if (newline(FFRAND, 1) == FALSE) + return (FALSE); + ++nline; + } else { + if (linsert(1, c) == FALSE) + return (FALSE); + } + ++i; + } + } + /* cosmetic adjustment */ + lp = curwp->w_linep; + + /* if offscreen insert */ + if (curwp->w_dotp == lp) { + while (nline-- && lback(lp) != curbp->b_headp) + lp = lback(lp); + /* adjust framing */ + curwp->w_linep = lp; + curwp->w_rflag |= WFFULL; + } + undo_boundary_enable(FFRAND, 1); + return (TRUE); +} + -- 2.40.0