mg

port of mg editor (openbsd to linux)
Log | Files | Refs | README

commit 92f6c3b8848b8fdabf150ee2bcddb22aa0379b20
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date:   Sat, 22 Nov 2014 00:05:09 +0100

initial import from openbsd 5.6 (2014-11-22)

Diffstat:
Makefile | 34++++++++++++++++++++++++++++++++++
README | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
autoexec.c | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
basic.c | 570+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bell.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
buffer.c | 1041+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
chrdef.h | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
cinfo.c | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
cmode.c | 526+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
cscope.c | 634+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
def.h | 729+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dir.c | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dired.c | 845+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
display.c | 1090+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
echo.c | 998+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
extend.c | 983+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
file.c | 739+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
fileio.c | 763+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
funmap.c | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
funmap.h | 9+++++++++
grep.c | 347+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
help.c | 231+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
kbd.c | 436+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
kbd.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
key.h | 14++++++++++++++
keymap.c | 574+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
line.c | 648+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
macro.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
macro.h | 21+++++++++++++++++++++
main.c | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
match.c | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
mg.1 | 1067+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modes.c | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
paragraph.c | 360+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
pathnames.h | 11+++++++++++
random.c | 508+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
re_search.c | 629+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
region.c | 672+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
search.c | 852+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
spawn.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
sysdef.h | 28++++++++++++++++++++++++++++
tags.c | 553+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
theo.c | 212+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tty.c | 447+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ttydef.h | 25+++++++++++++++++++++++++
ttyio.c | 233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ttykbd.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tutorial | 341+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
undo.c | 595+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
version.c | 24++++++++++++++++++++++++
window.c | 437+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
word.c | 351+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
yank.c | 266+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
53 files changed, 20717 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,34 @@ +# $OpenBSD: Makefile,v 1.28 2013/05/31 18:03:43 lum 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 bell.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 + +.include <bsd.prog.mk> diff --git a/README b/README @@ -0,0 +1,95 @@ +[This is an edited version of the original mg README, updated slightly to +reflect changes in the last 20 years.] + + +Mg (mg) is a Public Domain EMACS style editor. It is "broadly" +compatible with GNU Emacs, the latest creation of Richard M. +Stallman, Chief GNUisance and inventor of Emacs. GNU Emacs (and other +portions of GNU as they are released) are essentially free, (there are +handling charges for obtaining it) and so is Mg. You may never have +to learn another editor. (But probably will, at least long enough to +port Mg...) Mg was formerly named MicroGnuEmacs, the name change was +done at the request of Richard Stallman. + +Mg is not associated with the GNU project, and most of it does not +have the copyright restrictions present in GNU Emacs. (However, some +of the system dependent modules and the regular expression module do +have copyright notices. Look at the source code for exact +copyright restrictions.) The Mg authors individually may or may not +agree with the opinions expressed by Richard Stallman in "The GNU +Manifesto". + +This program is intended to be a small, fast, and portable editor for +people who can't (or don't want to) run real Emacs for one reason +or another. It is compatible with GNU because there shouldn't be +any reason to learn more than one Emacs flavor. + + +Beyond the work of Dave Conroy, author of the original public domain +v30, the current version contains the work of: + + blarson@ecla.usc.edu Bob Larson + mic@emx.utexas.edu Mic Kaczmarczik + mwm@violet.berkeley.edu Mike Meyer + sandra@cs.utah.edu Sandra Loosemore + mp1u+@andrew.cmu.edu Michael Portuesi + RCKG01M@CALSTATE.BITNET Stephen Walton + hakanson@mist.cs.orst.edu Marion Hakanson + +People who have worked on previous versions of Mg: + + rtech!daveb@sun.com Dave Brower + +Early release history: + +* Nov 16, 1986: First release to mod.sources +* Mar 3, 1987: First Release (mg1a) via comp.sources.unix +* May 26, 1988: Second release: (mg2a) via comp.sources.misc +* Jan 26, 1992: Linux port released by Charles Hedrick. This version + later makes its way onto tsx-11, Infomagic, and various other Linux + repositories. +* Feb 25, 2000: First import into the OpenBSD tree, where it is + currently maintained with contributions from many others. + +---------------------------------------------------------------------- + +Known limitations: + +Recursive bindings may cause help and key rebinding code to go into +an infinite loop, aborting with a stack overflow. + +Overwrite mode does not work in macros. (Characters are inserted +rather than overwriting.) + +Dired mode has some problems: Rename does not update the buffer. +Doing a dired again will update the buffer (whether it needs it or +not) and will lose any marks for deletion. .. and . are not +recognized as special cases. + +On systems with 16 bit integers, the kill buffer cannot exceed 32767 +bytes. + +Unlike GNU Emacs, Mg's minibuffer isn't multi-line aware and hence +some commands like "shell-command-on-region" always pop up a buffer to +display output irrespective of output's size. + +While navigating source code using Mg's cscope commands, the cursor +is always at the match location rather than in *cscope* buffer. Mg uses +the same keybindings of GNU Emacs's xcscope package for it's cscope commands. +As Mg's keybindings are case-insensitive some of the commands don't have a +default keybinding. + +New implementation oddities: + +insert and define-key are new commands corresponding to the mocklisp +functions in GNU Emacs. (Mg does not have non-command functions.) +(Mg's insert will only insert one string.) + +The display wrap code does not work at all like that of GNU emacs. + +Some commands that do not mimic emacs exactly don't have a "standard" +emacs name. For example 'backup-to-home-directory' is only a partial +implementation of emacs' range of commands that allow a user to +customise the backup file location. If a more complete implementation +were coded of these commands the non standard commands would probably +be removed. diff --git a/autoexec.c b/autoexec.c @@ -0,0 +1,111 @@ +/* $OpenBSD: autoexec.c,v 1.15 2014/10/11 03:03:44 doug Exp $ */ +/* this file is in the public domain */ +/* Author: Vincent Labrecque <vincent@openbsd.org> April 2002 */ + +#include "def.h" +#include "funmap.h" + +#include <fnmatch.h> + +struct autoexec { + SLIST_ENTRY(autoexec) next; /* link in the linked list */ + const char *pattern; /* Pattern to match to filenames */ + PF fp; +}; + +static SLIST_HEAD(, autoexec) autos; +static int ready; + + +#define AUTO_GROW 8 +/* + * Return a NULL terminated array of function pointers to be called + * when we open a file that matches <fname>. The list must be free(ed) + * after use. + */ +PF * +find_autoexec(const char *fname) +{ + PF *pfl, *npfl; + int have, used; + struct autoexec *ae; + + if (!ready) + return (NULL); + + pfl = NULL; + have = 0; + used = 0; + SLIST_FOREACH(ae, &autos, next) { + if (fnmatch(ae->pattern, fname, 0) == 0) { + if (used >= have) { + npfl = reallocarray(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/basic.c b/basic.c @@ -0,0 +1,570 @@ +/* $OpenBSD: basic.c,v 1.43 2014/11/16 04:16:41 guenther Exp $ */ + +/* This file is in the public domain */ + +/* + * Basic cursor motion commands. + * + * The routines in this file are the basic + * command functions for moving the cursor around on + * the screen, setting mark, and swapping dot with + * mark. Only moves between lines, which might make the + * current buffer framing bad, are hard. + */ +#include "def.h" + +#include <ctype.h> +#include <limits.h> + +/* + * Go to beginning of line. + */ +/* ARGSUSED */ +int +gotobol(int f, int n) +{ + curwp->w_doto = 0; + return (TRUE); +} + +/* + * Move cursor backwards. Do the + * right thing if the count is less than + * 0. Error if you try to move back from + * the beginning of the buffer. + */ +/* ARGSUSED */ +int +backchar(int f, int n) +{ + struct line *lp; + + if (n < 0) + return (forwchar(f, -n)); + while (n--) { + if (curwp->w_doto == 0) { + if ((lp = lback(curwp->w_dotp)) == curbp->b_headp) { + if (!(f & FFRAND)) { + dobeep(); + 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)) { + dobeep(); + 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) { + if (!(f & FFRAND)) { + dobeep(); + ewprintf("End of buffer"); + } + 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; + if (!(f & FFRAND)) { + dobeep(); + ewprintf("End of buffer"); + } + 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; + if (lback(dlp) == curbp->b_headp) { + if (!(f & FFRAND)) { + dobeep(); + ewprintf("Beginning of buffer"); + } + return(TRUE); + } + while (n-- && lback(dlp) != curbp->b_headp) { + dlp = lback(dlp); + curwp->w_dotline--; + } + if (n > 0 && !(f & FFRAND)) { + dobeep(); + ewprintf("Beginning of buffer"); + } + 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(curwp); /* 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) +{ + 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) { + dobeep(); + ewprintf("Line number %s", err); + return (FALSE); + } + } + return(setlineno(n)); +} + +/* + * Set the line number and switch to it. + */ +int +setlineno(int n) +{ + struct line *clp; + + 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/bell.c b/bell.c @@ -0,0 +1,58 @@ +/* $OpenBSD: bell.c,v 1.2 2014/03/22 11:05:37 lum Exp $ */ + +/* + * This file is in the public domain. + * + * Author: Mark Lumsden <mark@showcomplex.com> + * + */ + +/* + * Control how mg communicates with the user. + */ + +#include "def.h" + +void +bellinit(void) +{ + doaudiblebell = 1; + dovisiblebell = 0; +} + +void +dobeep(void) +{ + if (doaudiblebell) { + ttbeep(); + } + if (dovisiblebell) { + sgarbf = TRUE; + update(CNONE); + usleep(50000); + } +} + +/* ARGSUSED */ +int +toggleaudiblebell(int f, int n) +{ + if (f & FFARG) + doaudiblebell = n > 0; + else + doaudiblebell = !doaudiblebell; + + return (TRUE); +} + +/* ARGSUSED */ +int +togglevisiblebell(int f, int n) +{ + if (f & FFARG) + dovisiblebell = n > 0; + else + dovisiblebell = !dovisiblebell; + + return (TRUE); +} diff --git a/buffer.c b/buffer.c @@ -0,0 +1,1041 @@ +/* $OpenBSD: buffer.c,v 1.94 2014/06/12 16:29:41 bcallah Exp $ */ + +/* This file is in the public domain. */ + +/* + * Buffer handling. + */ + +#include "def.h" +#include "kbd.h" /* needed for modes */ + +#include <libgen.h> +#include <stdarg.h> + +#ifndef DIFFTOOL +#define DIFFTOOL "/usr/bin/diff" +#endif /* !DIFFTOOL */ + +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 (unless called by dired mode). 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 (f & FFRAND) /* dired mode 'q' */ + bp = curbp; + else 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; + + /* + * 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; + } + + while ((rec = TAILQ_FIRST(&bp->b_undo))) { + TAILQ_REMOVE(&bp->b_undo, rec, next); + free_undo_record(rec); + } + + 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] == '$') { + dobeep(); + 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, save2 = FALSE, ret; + char pbuf[NFILEN + 11]; + + for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { + if (*(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)) { + dobeep(); + ewprintf("Error: filename too long!"); + return (UERROR); + } + if ((f == TRUE || (save = eyorn(pbuf)) == TRUE) && + (save2 = buffsave(bp)) == TRUE) { + bp->b_flag &= ~BFCHG; + upmodes(bp); + } else { + if (save2 == FIOERR) + return (save2); + 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) { + dobeep(); + 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; + 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) { + dobeep(); + 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; + 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) { + dobeep(); + 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 & (BFCHG | BFDIRTY)) == 0) + if (fchecktime(bp) != TRUE) + bp->b_flag |= BFDIRTY; + + if ((bp->b_flag & (BFDIRTY | BFIGNDIRTY)) == BFDIRTY) { + s = eynorr("File changed on disk; really edit the buffer"); + switch (s) { + case TRUE: + bp->b_flag &= ~BFDIRTY; + bp->b_flag |= BFIGNDIRTY; + return (TRUE); + case REVERT: + dorevert(); + return (FALSE); + default: + return (s); + } + } + + return (TRUE); +} + +/* + * Revert the current buffer to whatever is on disk. + */ +/* ARGSUSED */ +int +revertbuffer(int f, int n) +{ + char fbuf[NFILEN + 32]; + + if (curbp->b_fname[0] == 0) { + dobeep(); + ewprintf("Cannot revert buffer not associated with any files."); + return (FALSE); + } + + snprintf(fbuf, sizeof(fbuf), "Revert buffer from file %s", + curbp->b_fname); + + if (eyorn(fbuf) == TRUE) + return dorevert(); + + return (FALSE); +} + +int +dorevert(void) +{ + int lineno; + struct undo_rec *rec; + + if (access(curbp->b_fname, F_OK|R_OK) != 0) { + dobeep(); + if (errno == ENOENT) + ewprintf("File %s no longer exists!", + curbp->b_fname); + else + ewprintf("File %s is no longer readable!", + curbp->b_fname); + return (FALSE); + } + + /* Save our current line, so we can go back after reloading. */ + lineno = curwp->w_dotline; + + /* Prevent readin from asking if we want to kill the buffer. */ + curbp->b_flag &= ~BFCHG; + + /* Clean up undo memory */ + while ((rec = TAILQ_FIRST(&curbp->b_undo))) { + TAILQ_REMOVE(&curbp->b_undo, rec, next); + free_undo_record(rec); + } + + if (readin(curbp->b_fname)) + return(setlineno(lineno)); + return (FALSE); +} + +/* + * Diff the current buffer to what is on disk. + */ +/*ARGSUSED */ +int +diffbuffer(int f, int n) +{ + struct buffer *bp; + struct line *lp, *lpend; + size_t len; + int ret; + char *text, *ttext; + char * const argv[] = + {DIFFTOOL, "-u", "-p", curbp->b_fname, "-", (char *)NULL}; + + len = 0; + + /* C-u is not supported */ + if (n > 1) + return (ABORT); + + if (access(DIFFTOOL, X_OK) != 0) { + dobeep(); + ewprintf("%s not found or not executable.", DIFFTOOL); + return (FALSE); + } + + if (curbp->b_fname[0] == 0) { + dobeep(); + ewprintf("Cannot diff buffer not associated with any files."); + return (FALSE); + } + + lpend = curbp->b_headp; + for (lp = lforw(lpend); lp != lpend; lp = lforw(lp)) { + len+=llength(lp); + if (lforw(lp) != lpend) /* no implied \n on last line */ + len++; + } + if ((text = calloc(len + 1, sizeof(char))) == NULL) { + dobeep(); + ewprintf("Cannot allocate memory."); + return (FALSE); + } + ttext = text; + + for (lp = lforw(lpend); lp != lpend; lp = lforw(lp)) { + if (llength(lp) != 0) { + memcpy(ttext, ltext(lp), llength(lp)); + ttext += llength(lp); + } + if (lforw(lp) != lpend) /* no implied \n on last line */ + *ttext++ = '\n'; + } + + bp = bfind("*Diff*", TRUE); + bp->b_flag |= BFREADONLY; + if (bclear(bp) != TRUE) { + free(text); + return (FALSE); + } + + ret = pipeio(DIFFTOOL, argv, text, len, bp); + + if (ret == TRUE) { + eerase(); + if (lforw(bp->b_headp) == bp->b_headp) + addline(bp, "Diff finished (no differences)."); + } + + free(text); + return (ret); +} + +/* + * 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)) { + dobeep(); + 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); +} diff --git a/chrdef.h b/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/cinfo.c b/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/cmode.c b/cmode.c @@ -0,0 +1,526 @@ +/* $OpenBSD: cmode.c,v 1.10 2014/11/16 01:08:36 guenther Exp $ */ +/* + * This file is in the public domain. + * + * Author: Kjell Wooding <kjell@openbsd.org> + */ + +/* + * Implement an non-irritating KNF-compliant mode for editing + * C code. + */ +#include <ctype.h> + +#include "def.h" +#include "kbd.h" +#include "funmap.h" + +/* Pull in from modes.c */ +extern int changemode(int, int, char *); + +static int cc_strip_trailp = TRUE; /* Delete Trailing space? */ +static int cc_basic_indent = 8; /* Basic Indent multiple */ +static int cc_cont_indent = 4; /* Continued line indent */ +static int cc_colon_indent = -8; /* Label / case indent */ + +static int getmatch(int, int); +static int getindent(const struct line *, int *); +static int in_whitespace(struct line *, int); +static int findcolpos(const struct buffer *, const struct line *, int); +static struct line *findnonblank(struct line *); +static int isnonblank(const struct line *, int); + +void cmode_init(void); +int cc_comment(int, int); + +/* Keymaps */ + +static PF cmode_brace[] = { + cc_brace, /* } */ +}; + +static PF cmode_cCP[] = { + compile, /* C-c P */ +}; + + +static PF cmode_cc[] = { + NULL, /* ^C */ + rescan, /* ^D */ + rescan, /* ^E */ + rescan, /* ^F */ + rescan, /* ^G */ + rescan, /* ^H */ + cc_tab, /* ^I */ + rescan, /* ^J */ + rescan, /* ^K */ + rescan, /* ^L */ + cc_lfindent, /* ^M */ +}; + +static PF cmode_spec[] = { + cc_char, /* : */ +}; + +static struct KEYMAPE (1 + IMAPEXT) cmode_cmap = { + 1, + 1 + IMAPEXT, + rescan, + { + { 'P', 'P', cmode_cCP, NULL } + } +}; + +static struct KEYMAPE (3 + IMAPEXT) cmodemap = { + 3, + 3 + IMAPEXT, + rescan, + { + { CCHR('C'), CCHR('M'), cmode_cc, (KEYMAP *) &cmode_cmap }, + { ':', ':', cmode_spec, NULL }, + { '}', '}', cmode_brace, NULL } + } +}; + +/* Funtion, Mode hooks */ + +void +cmode_init(void) +{ + funmap_add(cmode, "c-mode"); + funmap_add(cc_char, "c-handle-special-char"); + funmap_add(cc_brace, "c-handle-special-brace"); + funmap_add(cc_tab, "c-tab-or-indent"); + funmap_add(cc_indent, "c-indent"); + funmap_add(cc_lfindent, "c-indent-and-newline"); + maps_add((KEYMAP *)&cmodemap, "c"); +} + +/* + * Enable/toggle c-mode + */ +int +cmode(int f, int n) +{ + return(changemode(f, n, "c")); +} + +/* + * Handle special C character - selfinsert then indent. + */ +int +cc_char(int f, int n) +{ + if (n < 0) + return (FALSE); + if (selfinsert(FFRAND, n) == FALSE) + return (FALSE); + return (cc_indent(FFRAND, n)); +} + +/* + * Handle special C character - selfinsert then indent. + */ +int +cc_brace(int f, int n) +{ + if (n < 0) + return (FALSE); + if (showmatch(FFRAND, 1) == FALSE) + return (FALSE); + return (cc_indent(FFRAND, n)); +} + + +/* + * If we are in the whitespace at the beginning of the line, + * simply act as a regular tab. If we are not, indent + * current line according to whitespace rules. + */ +int +cc_tab(int f, int n) +{ + int inwhitep = FALSE; /* In leading whitespace? */ + + inwhitep = in_whitespace(curwp->w_dotp, llength(curwp->w_dotp)); + + /* If empty line, or in whitespace */ + if (llength(curwp->w_dotp) == 0 || inwhitep) + return (selfinsert(f, n)); + + return (cc_indent(FFRAND, 1)); +} + +/* + * Attempt to indent current line according to KNF rules. + */ +int +cc_indent(int f, int n) +{ + int pi, mi; /* Previous indents (mi is ignored) */ + int ci; /* current indent */ + 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 */ + (void)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. + */ + 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/cscope.c b/cscope.c @@ -0,0 +1,634 @@ +/* $OpenBSD: cscope.c,v 1.8 2014/11/16 04:16:41 guenther Exp $ */ + +/* + * This file is in the public domain. + * + * Author: Sunil Nimmagadda <sunil@sunilnimmagadda.com> + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/queue.h> + +#include <ctype.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "def.h" + +#define CSSYMBOL 0 +#define CSDEFINITION 1 +#define CSCALLEDFUNCS 2 +#define CSCALLERFUNCS 3 +#define CSTEXT 4 +#define CSEGREP 6 +#define CSFINDFILE 7 +#define CSINCLUDES 8 + +struct cstokens { + const char *fname; + const char *function; + const char *lineno; + const char *pattern; +}; + +struct csmatch { + TAILQ_ENTRY(csmatch) entry; + int lineno; +}; + +struct csrecord { + TAILQ_ENTRY(csrecord) entry; + char *filename; + TAILQ_HEAD(matches, csmatch) matches; +}; + +static TAILQ_HEAD(csrecords, csrecord) csrecords = TAILQ_HEAD_INITIALIZER(csrecords); +static struct csrecord *addentryr; +static struct csrecord *currecord; +static struct csmatch *curmatch; +static const char *addentryfn; +static const char *csprompt[] = { + "Find this symbol: ", + "Find this global definition: ", + "Find functions called by this function: ", + "Find functions calling this function: ", + "Find this text string: ", + "Change this text string: ", + "Find this egrep pattern: ", + "Find this file: ", + "Find files #including this file: " +}; + +static int addentry(struct buffer *, char *); +static void csflush(void); +static int do_cscope(int); +static int csexists(const char *); +static int getattr(char *, struct cstokens *); +static int jumptomatch(void); +static void prettyprint(struct buffer *, struct cstokens *); +static const char *ltrim(const char *); + +/* + * Find this symbol. Bound to C-c s s + */ +/* ARGSUSED */ +int +cssymbol(int f, int n) +{ + return (do_cscope(CSSYMBOL)); +} + +/* + * Find this global definition. Bound to C-c s d + */ +/* ARGSUSED */int +csdefinition(int f, int n) +{ + return (do_cscope(CSDEFINITION)); +} + +/* + * Find functions called by this function. Bound to C-c s l + */ +/* ARGSUSED */ +int +csfuncalled(int f, int n) +{ + return (do_cscope(CSCALLEDFUNCS)); +} + +/* + * Find functions calling this function. Bound to C-c s c + */ +/* ARGSUSED */ +int +cscallerfuncs(int f, int n) +{ + return (do_cscope(CSCALLERFUNCS)); +} + +/* + * Find this text. Bound to C-c s t + */ +/* ARGSUSED */ +int +csfindtext(int f, int n) +{ + return (do_cscope(CSTEXT)); +} + +/* + * Find this egrep pattern. Bound to C-c s e + */ +/* ARGSUSED */ +int +csegrep(int f, int n) +{ + return (do_cscope(CSEGREP)); +} + +/* + * Find this file. Bound to C-c s f + */ +/* ARGSUSED */ +int +csfindfile(int f, int n) +{ + return (do_cscope(CSFINDFILE)); +} + +/* + * Find files #including this file. Bound to C-c s i + */ +/* ARGSUSED */ +int +csfindinc(int f, int n) +{ + return (do_cscope(CSINCLUDES)); +} + +/* + * Create list of files to index in the given directory + * using cscope-indexer. + */ +/* ARGSUSED */ +int +cscreatelist(int f, int n) +{ + struct buffer *bp; + struct stat sb; + FILE *fpipe; + char dir[NFILEN], cmd[BUFSIZ], title[BUFSIZ], *line, *bufp; + size_t len; + int clen; + + if (getbufcwd(dir, sizeof(dir)) == FALSE) + dir[0] = '\0'; + + bufp = eread("Index files in directory: ", dir, + sizeof(dir), EFCR | EFDEF | EFNEW | EFNUL); + + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + + if (stat(dir, &sb) == -1) { + dobeep(); + ewprintf("stat: %s", strerror(errno)); + return (FALSE); + } else if (S_ISDIR(sb.st_mode) == 0) { + dobeep(); + ewprintf("%s: Not a directory", dir); + return (FALSE); + } + + if (csexists("cscope-indexer") == FALSE) { + dobeep(); + 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) { + dobeep(); + ewprintf("problem opening pipe"); + return (FALSE); + } + + bp = bfind("*cscope*", TRUE); + if (bclear(bp) != TRUE) { + pclose(fpipe); + 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)) { + pclose(fpipe); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + ewprintf("The *cscope* buffer does not exist yet"); + return (FALSE); + } + + } else { + if ((r = TAILQ_NEXT(currecord, entry)) == NULL) { + dobeep(); + 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) { + dobeep(); + ewprintf("The *cscope* buffer does not exist yet"); + return (FALSE); + } + + } else { + if ((r = TAILQ_PREV(currecord, csrecords, entry)) == NULL) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + ewprintf("problem opening pipe"); + return (FALSE); + } + + bp = bfind("*cscope*", TRUE); + if (bclear(bp) != TRUE) { + pclose(fpipe); + return (FALSE); + } + bp->b_flag |= BFREADONLY; + + clen = snprintf(title, sizeof(title), "%s%s", csprompt[i], pattern); + if (clen < 0 || clen >= sizeof(title)) { + pclose(fpipe); + return (FALSE); + } + addline(bp, title); + addline(bp, ""); + addline(bp, "-------------------------------------------------------------------------------"); + /* All lines are NUL terminated */ + while ((buf = fgetln(fpipe, &len)) != NULL) { + buf[len - 1] = '\0'; + if (addentry(bp, buf) != TRUE) + return (FALSE); + nores = 1; + }; + pclose(fpipe); + addline(bp, "-------------------------------------------------------------------------------"); + if (nores == 0) + ewprintf("No matches were found."); + return (popbuftop(bp, WNONE)); +} + +/* + * For each line read from cscope output, extract the tokens, + * add them to list and pretty print a line in *cscope* buffer. + */ +int +addentry(struct buffer *bp, char *csline) +{ + struct csrecord *r; + struct csmatch *m; + struct cstokens t; + int lineno; + char buf[BUFSIZ]; + const char *errstr; + + r = NULL; + if (getattr(csline, &t) == FALSE) + return (FALSE); + + lineno = strtonum(t.lineno, INT_MIN, INT_MAX, &errstr); + if (errstr) + return (FALSE); + + if (addentryfn == NULL || strcmp(addentryfn, t.fname) != 0) { + if ((r = malloc(sizeof(struct csrecord))) == NULL) + return (FALSE); + addentryr = r; + if ((r->filename = strndup(t.fname, NFILEN)) == NULL) + goto cleanup; + addentryfn = r->filename; + TAILQ_INIT(&r->matches); + if ((m = malloc(sizeof(struct csmatch))) == NULL) + goto cleanup; + m->lineno = lineno; + TAILQ_INSERT_TAIL(&r->matches, m, entry); + TAILQ_INSERT_TAIL(&csrecords, r, entry); + addline(bp, ""); + if (snprintf(buf, sizeof(buf), "*** %s", t.fname) < 0) + goto cleanup; + addline(bp, buf); + } else { + if ((m = malloc(sizeof(struct csmatch))) == NULL) + goto cleanup; + m->lineno = lineno; + TAILQ_INSERT_TAIL(&addentryr->matches, m, entry); + } + prettyprint(bp, &t); + return (TRUE); +cleanup: + free(r); + return (FALSE); +} + +/* + * Cscope line: <filename> <function> <lineno> <pattern> + */ +int +getattr(char *line, struct cstokens *t) +{ + char *p; + + if ((p = strchr(line, ' ')) == NULL) + return (FALSE); + *p++ = '\0'; + t->fname = line; + line = p; + + if ((p = strchr(line, ' ')) == NULL) + return (FALSE); + *p++ = '\0'; + t->function = line; + line = p; + + if ((p = strchr(line, ' ')) == NULL) + return (FALSE); + *p++ = '\0'; + t->lineno = line; + + if (*p == '\0') + return (FALSE); + t->pattern = p; + + return (TRUE); +} + +void +prettyprint(struct buffer *bp, struct cstokens *t) +{ + char buf[BUFSIZ]; + + if (snprintf(buf, sizeof(buf), "%s[%s]\t\t%s", + t->function, t->lineno, ltrim(t->pattern)) < 0) + return; + addline(bp, buf); +} + +const char * +ltrim(const char *s) +{ + while (isblank((unsigned char)*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) { + dobeep(); + 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)) { + dobeep(); + 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/def.h b/def.h @@ -0,0 +1,729 @@ +/* $OpenBSD: def.h,v 1.141 2014/04/03 20:17:12 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 UERROR 3 /* User Error. */ +#define REVERT 4 /* Revert the buffer */ + +#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 */ + 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); +int makedir(int, int); +int do_makedir(char *); +int ask_makedir(void); + +/* 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); +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 *); +int revertbuffer(int, int); +int dorevert(void); +int diffbuffer(int, int); +struct buffer *findbuffer(char *); + +/* display.c */ +int vtresize(int, int, int); +void vtinit(void); +void vttidy(void); +void update(int); +int linenotoggle(int, int); +int colnotoggle(int, int); + +/* echo.c X */ +void eerase(void); +int eyorn(const char *); +int eynorr(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); +char *expandtilde(const char *); + +/* 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); +int setlineno(int); + +/* random.c X */ +int showcpos(int, int); +int getcolpos(struct mgwin *); +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); +int shellcommand(int, int); +int pipeio(const char * const, char * const[], char * const, int, + struct buffer *); + +/* 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); + +/* bell.c */ +void bellinit(void); +int toggleaudiblebell(int, int); +int togglevisiblebell(int, int); +void dobeep(void); + +/* + * 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 int doaudiblebell; +extern int dovisiblebell; +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/dir.c b/dir.c @@ -0,0 +1,176 @@ +/* $OpenBSD: dir.c,v 1.27 2014/04/03 20:17:12 lum 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 <sys/stat.h> + +#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) { + dobeep(); + 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); +} + +/* Create the directory and it's parents. */ +/* ARGSUSED */ +int +makedir(int f, int n) +{ + return (ask_makedir()); +} + +int +ask_makedir(void) +{ + + char bufc[NFILEN]; + char *path; + + if (getbufcwd(bufc, sizeof(bufc)) != TRUE) + return (ABORT); + if ((path = eread("Make directory: ", bufc, NFILEN, + EFDEF | EFNEW | EFCR | EFFILE)) == NULL) + return (ABORT); + else if (path[0] == '\0') + return (FALSE); + + return (do_makedir(path)); +} + +int +do_makedir(char *path) +{ + struct stat sb; + int finished, ishere; + mode_t dir_mode, mode, oumask; + char *slash; + + if ((path = adjustname(path, TRUE)) == NULL) + return (FALSE); + + /* Remove trailing slashes */ + slash = strrchr(path, '\0'); + while (--slash > path && *slash == '/') + *slash = '\0'; + + slash = path; + + oumask = umask(0); + mode = 0777 & ~oumask; + dir_mode = mode | S_IWUSR | S_IXUSR; + + for (;;) { + slash += strspn(slash, "/"); + slash += strcspn(slash, "/"); + + finished = (*slash == '\0'); + *slash = '\0'; + + ishere = !stat(path, &sb); + if (finished && ishere) { + dobeep(); + ewprintf("Cannot create directory %s: file exists", + path); + return(FALSE); + } else if (!finished && ishere && S_ISDIR(sb.st_mode)) { + *slash = '/'; + continue; + } + + if (mkdir(path, finished ? mode : dir_mode) == 0) { + if (mode > 0777 && chmod(path, mode) < 0) { + umask(oumask); + return (ABORT); + } + } else { + if (!ishere || !S_ISDIR(sb.st_mode)) { + if (!ishere) { + dobeep(); + ewprintf("Creating directory: " + "permission denied, %s", path); + } else + eerase(); + + umask(oumask); + return (FALSE); + } + } + + if (finished) + break; + + *slash = '/'; + } + + eerase(); + umask(oumask); + return (TRUE); +} diff --git a/dired.c b/dired.c @@ -0,0 +1,845 @@ +/* $OpenBSD: dired.c,v 1.68 2014/11/16 04:16:41 guenther Exp $ */ + +/* This file is in the public domain. */ + +/* dired module for mg 2a + * by Robert A. Larson + */ + +#include "def.h" +#include "funmap.h" +#include "kbd.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/wait.h> + +#include <ctype.h> +#include <limits.h> +#include <signal.h> +#include <fcntl.h> +#include <err.h> +#include <errno.h> +#include <libgen.h> +#include <stdarg.h> + +void dired_init(void); +static int dired(int, int); +static int d_otherwindow(int, int); +static int d_undel(int, int); +static int d_undelbak(int, int); +static int d_findfile(int, int); +static int d_ffotherwindow(int, int); +static int d_expunge(int, int); +static int d_copy(int, int); +static int d_del(int, int); +static int d_rename(int, int); +static int d_exec(int, struct buffer *, const char *, const char *, ...); +static int d_shell_command(int, int); +static int d_create_directory(int, int); +static int d_makename(struct line *, char *, size_t); +static int d_warpdot(struct line *, int *); +static int d_forwpage(int, int); +static int d_backpage(int, int); +static int d_forwline(int, int); +static int d_backline(int, int); +static int d_killbuffer_cmd(int, int); +static int d_refreshbuffer(int, int); +static void reaper(int); +static struct buffer *refreshbuffer(struct buffer *); + +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 */ + d_refreshbuffer /* g */ +}; + +static PF diredn[] = { + d_forwline, /* n */ + d_ffotherwindow, /* o */ + d_backline, /* p */ + d_killbuffer_cmd, /* 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', 'g', 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-unmark-backward"); + funmap_add(d_create_directory, "dired-create-directory"); + funmap_add(d_copy, "dired-do-copy"); + funmap_add(d_expunge, "dired-do-flagged-delete"); + funmap_add(d_findfile, "dired-find-file"); + funmap_add(d_ffotherwindow, "dired-find-file-other-window"); + funmap_add(d_del, "dired-flag-file-deletion"); + 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-do-rename"); + funmap_add(d_backpage, "dired-scroll-down"); + funmap_add(d_forwpage, "dired-scroll-up"); + funmap_add(d_undel, "dired-unmark"); + funmap_add(d_killbuffer_cmd, "quit-window"); + 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; + return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); +} + +/* 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; + return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); +} + +/* ARGSUSED */ +int +d_undelbak(int f, int n) +{ + if (n < 0) + return (d_undel(f, -n)); + while (n--) { + if (lback(curwp->w_dotp) != curbp->b_headp) + curwp->w_dotp = lback(curwp->w_dotp); + if (llength(curwp->w_dotp) > 0) + lputc(curwp->w_dotp, 0, ' '); + } + curwp->w_rflag |= WFEDIT | WFMOVE; + return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); +} + +/* 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: + dobeep(); + ewprintf("Bad line in dired buffer"); + return (FALSE); + case FALSE: + if (unlink(fname) < 0) { + (void)xbasename(sname, fname, NFILEN); + dobeep(); + ewprintf("Could not delete '%s'", sname); + return (FALSE); + } + break; + case TRUE: + if (rmdir(fname) < 0) { + (void)xbasename(sname, fname, NFILEN); + dobeep(); + 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]; + char *topath, *bufp; + int ret; + size_t off; + struct buffer *bp; + + if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) { + dobeep(); + ewprintf("Not a file"); + return (FALSE); + } + off = strlcpy(toname, curbp->b_fname, sizeof(toname)); + if (off >= sizeof(toname) - 1) { /* can't happen, really */ + dobeep(); + 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); + + topath = adjustname(toname, TRUE); + ret = (copy(frname, topath) >= 0) ? TRUE : FALSE; + if (ret != TRUE) + return (ret); + if ((bp = refreshbuffer(curbp)) == NULL) + return (FALSE); + return (showbuffer(bp, curwp, WFFULL | WFMODE)); +} + +/* ARGSUSED */ +int +d_rename(int f, int n) +{ + char frname[NFILEN], toname[NFILEN]; + char *topath, *bufp; + int ret; + size_t off; + struct buffer *bp; + char sname[NFILEN]; + + if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) { + dobeep(); + ewprintf("Not a file"); + return (FALSE); + } + off = strlcpy(toname, curbp->b_fname, sizeof(toname)); + if (off >= sizeof(toname) - 1) { /* can't happen, really */ + dobeep(); + 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); + + topath = adjustname(toname, TRUE); + ret = (rename(frname, topath) >= 0) ? TRUE : FALSE; + if (ret != TRUE) + return (ret); + if ((bp = refreshbuffer(curbp)) == NULL) + return (FALSE); + 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[PATH_MAX], *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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + ewprintf("Can't open input file : %s", strerror(errno)); + goto out; + } + + if (pipe(fds) == -1) { + dobeep(); + 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) { + dobeep(); + 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) +{ + int ret; + struct buffer *bp; + + ret = ask_makedir(); + if (ret != TRUE) + return(ret); + + if ((bp = refreshbuffer(curbp)) == NULL) + return (FALSE); + + return (showbuffer(bp, curwp, WFFULL | WFMODE)); +} + +/* ARGSUSED */ +int +d_killbuffer_cmd(int f, int n) +{ + return(killbuffer_cmd(FFRAND, 0)); +} + +int +d_refreshbuffer(int f, int n) +{ + struct buffer *bp; + + if ((bp = refreshbuffer(curbp)) == NULL) + return (FALSE); + + return (showbuffer(bp, curwp, WFFULL | WFMODE)); +} + +struct buffer * +refreshbuffer(struct buffer *bp) +{ + char *tmp; + + tmp = strdup(bp->b_fname); + if (tmp == NULL) { + dobeep(); + ewprintf("Out of memory"); + return (NULL); + } + + killbuffer(bp); + + /* dired_() uses findbuffer() to create new buffer */ + if ((bp = dired_(tmp)) == NULL) { + free(tmp); + return (NULL); + } + free(tmp); + curbp = bp; + + return (bp); +} + +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 i; + size_t len; + + if ((dname = adjustname(dname, FALSE)) == NULL) { + dobeep(); + 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 ((access(dname, R_OK | X_OK)) == -1) { + if (errno == EACCES) { + dobeep(); + ewprintf("Permission denied"); + } + return (NULL); + } + if ((bp = findbuffer(dname)) == NULL) { + dobeep(); + ewprintf("Could not create buffer"); + return (NULL); + } + if (bclear(bp) != TRUE) + return (NULL); + bp->b_flag |= BFREADONLY | BFIGNDIRTY; + + 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"); + dobeep(); + ewprintf("Could not find mode dired"); + return (NULL); + } + bp->b_nmodes = 1; + return (bp); +} diff --git a/display.c b/display.c @@ -0,0 +1,1090 @@ +/* $OpenBSD: display.c,v 1.42 2014/10/16 17:36:11 deraadt Exp $ */ + +/* This file is in the public domain. */ + +/* + * The functions in this file handle redisplay. The + * redisplay system knows almost nothing about the editing + * process; the editing functions do, however, set some + * hints to eliminate a lot of the grinding. There is more + * that can be done; the "vtputc" interface is a real + * pig. + */ +#include "def.h" +#include "kbd.h" + +#include <ctype.h> + +/* + * You can change these back to the types + * implied by the name if you get tight for space. If you + * make both of them "int" you get better code on the VAX. + * They do nothing if this is not Gosling redisplay, except + * for change the size of a structure that isn't used. + * A bit of a cheat. + */ +#define XCHAR int +#define XSHORT int + +#ifdef STANDOUT_GLITCH +#include <term.h> +#endif + +/* + * A video structure always holds + * an array of characters whose length is equal to + * the longest line possible. v_text is allocated + * dynamically to fit the screen width. + */ +struct video { + short v_hash; /* Hash code, for compares. */ + short v_flag; /* Flag word. */ + short v_color; /* Color of the line. */ + XSHORT v_cost; /* Cost of display. */ + char *v_text; /* The actual characters. */ +}; + +#define VFCHG 0x0001 /* Changed. */ +#define VFHBAD 0x0002 /* Hash and cost are bad. */ +#define VFEXT 0x0004 /* extended line (beond ncol) */ + +/* + * SCORE structures hold the optimal + * trace trajectory, and the cost of redisplay, when + * the dynamic programming redisplay code is used. + * If no fancy redisplay, this isn't used. The trace index + * fields can be "char", and the cost a "short", but + * this makes the code worse on the VAX. + */ +struct score { + XCHAR s_itrace; /* "i" index for track back. */ + XCHAR s_jtrace; /* "j" index for trace back. */ + XSHORT s_cost; /* Display cost. */ +}; + +void vtmove(int, int); +void vtputc(int); +void vtpute(int); +int vtputs(const char *); +void vteeol(void); +void updext(int, int); +void modeline(struct mgwin *, int); +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; +static int colnos = FALSE; + +/* 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); +} + +/* ARGSUSED */ +int +colnotoggle(int f, int n) +{ + if (f & FFARG) + colnos = n > 0; + else + colnos = !colnos; + + 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) + +#define TRYREALLOCARRAY(a, n, m) do { \ + void *tmp; \ + if ((tmp = reallocarray((a), (n), (m))) == 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; + } + } + + TRYREALLOCARRAY(score, newrow * newrow, sizeof(struct score)); + TRYREALLOCARRAY(vscreen, (newrow - 1), sizeof(struct video *)); + TRYREALLOCARRAY(pscreen, (newrow - 1), sizeof(struct video *)); + TRYREALLOCARRAY(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); + TRYREALLOC(blanks.v_text, newcol); + } + + 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(int modelinecolor) +{ + 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 || colnos) { + 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, modelinecolor); + 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 modelinecolor) +{ + 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 = modelinecolor; /* 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 && colnos) + len = snprintf(sl, sizeof(sl), "--L%d--C%d", wp->w_dotline, + getcolpos(wp)); + else if (linenos) + len = snprintf(sl, sizeof(sl), "--L%d", wp->w_dotline); + else if (colnos) + len = snprintf(sl, sizeof(sl), "--C%d", getcolpos(wp)); + if ((linenos || colnos) && 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/echo.c b/echo.c @@ -0,0 +1,998 @@ +/* $OpenBSD: echo.c,v 1.57 2014/11/16 04:16:41 guenther Exp $ */ + +/* This file is in the public domain. */ + +/* + * Echo line reading and writing. + * + * Common routines for reading and writing characters in the echo line area + * of the display screen. Used by the entire known universe. + */ + +#include "def.h" +#include "key.h" +#include "macro.h" + +#include "funmap.h" + +#include <stdarg.h> +#include <term.h> + +static char *veread(const char *, char *, size_t, int, va_list); +static int complt(int, int, char *, size_t, int, int *); +static int complt_list(int, char *, int); +static void eformat(const char *, va_list); +static void eputi(int, int); +static void eputl(long, int); +static void eputs(const char *); +static void eputc(char); +static struct list *copy_list(struct list *); + +int epresf = FALSE; /* stuff in echo line flag */ + +/* + * Erase the echo line. + */ +void +eerase(void) +{ + ttcolor(CTEXT); + ttmove(nrow - 1, 0); + tteeol(); + ttflush(); + epresf = FALSE; +} + +/* + * Ask a "yes" or "no" question. Return ABORT if the user answers the + * question with the abort ("^G") character. Return FALSE for "no" and + * TRUE for "yes". No formatting services are available. No newline + * required. + */ +int +eyorn(const char *sp) +{ + int s; + + if (inmacro) + return (TRUE); + + ewprintf("%s? (y or n) ", sp); + for (;;) { + s = getkey(FALSE); + if (s == 'y' || s == 'Y' || s == ' ') + return (TRUE); + if (s == 'n' || s == 'N' || s == CCHR('M')) + return (FALSE); + if (s == CCHR('G')) + return (ctrlg(FFRAND, 1)); + ewprintf("Please answer y or n. %s? (y or n) ", sp); + } + /* NOTREACHED */ +} + +/* + * Ask a "yes", "no" or "revert" question. Return ABORT if the user answers + * the question with the abort ("^G") character. Return FALSE for "no", + * TRUE for "yes" and REVERT for "revert". No formatting services are + * available. No newline required. + */ +int +eynorr(const char *sp) +{ + int s; + + if (inmacro) + return (TRUE); + + ewprintf("%s? (y, n or r) ", 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 == 'r' || s == 'R') + return (REVERT); + if (s == CCHR('G')) + return (ctrlg(FFRAND, 1)); + ewprintf("Please answer y, n or r."); + } + /* 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 == 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(); + 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) { + dobeep(); + 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) { + dobeep(); + 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); + dobeep(); + 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 = (ncol > maxwidth ? ncol : maxwidth) + 1; + if ((linebuf = malloc(linesize)) == NULL) { + free_file_list(wholelist); + 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(CMODE); /* 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/extend.c b/extend.c @@ -0,0 +1,983 @@ +/* $OpenBSD: extend.c,v 1.57 2014/11/16 04:16:41 guenther Exp $ */ + +/* This file is in the public domain. */ + +/* + * Extended (M-X) commands, rebinding, and startup file processing. + */ +#include "chrdef.h" +#include "def.h" +#include "kbd.h" +#include "funmap.h" + +#include <sys/types.h> +#include <ctype.h> +#include <limits.h> + +#include "macro.h" + +#ifdef FKEYS +#include "key.h" +#ifndef BINDKEY +#define BINDKEY /* bindkey is used by FKEYS startup code */ +#endif /* !BINDKEY */ +#endif /* FKEYS */ + +#include <ctype.h> + +static int remap(KEYMAP *, int, PF, KEYMAP *); +static KEYMAP *reallocmap(KEYMAP *); +static void fixmap(KEYMAP *, KEYMAP *, KEYMAP *); +static int dobind(KEYMAP *, const char *, int); +static char *skipwhite(char *); +static char *parsetoken(char *); +#ifdef BINDKEY +static int bindkey(KEYMAP **, const char *, KCHAR *, int); +#endif /* BINDKEY */ + +/* + * Insert a string, mainly for use from macros (created by selfinsert). + */ +/* ARGSUSED */ +int +insert(int f, int n) +{ + char buf[128], *bufp, *cp; + int count, c; + + if (inmacro) { + while (--n >= 0) { + for (count = 0; count < maclcur->l_used; count++) { + if ((((c = maclcur->l_text[count]) == '\n') + ? lnewline() : linsert(1, c)) != TRUE) + return (FALSE); + } + } + maclcur = maclcur->l_fp; + return (TRUE); + } + if (n == 1) + /* CFINS means selfinsert can tack on the end */ + thisflag |= CFINS; + + if ((bufp = eread("Insert: ", buf, sizeof(buf), EFNEW)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + while (--n >= 0) { + cp = buf; + while (*cp) { + if (((*cp == '\n') ? lnewline() : linsert(1, *cp)) + != TRUE) + return (FALSE); + cp++; + } + } + return (TRUE); +} + +/* + * Bind a key to a function. Cases range from the trivial (replacing an + * existing binding) to the extremely complex (creating a new prefix in a + * map_element that already has one, so the map_element must be split, + * but the keymap doesn't have enough room for another map_element, so + * the keymap is reallocated). No attempt is made to reclaim space no + * longer used, if this is a problem flags must be added to indicate + * malloced versus static storage in both keymaps and map_elements. + * Structure assignments would come in real handy, but K&R based compilers + * don't have them. Care is taken so running out of memory will leave + * the keymap in a usable state. + * Parameters are: + * curmap: pointer to the map being changed + * c: character being changed + * funct: function being changed to + * pref_map: if funct==NULL, map to bind to or NULL for new + */ +static int +remap(KEYMAP *curmap, int c, PF funct, KEYMAP *pref_map) +{ + int i, n1, n2, nold; + KEYMAP *mp, *newmap; + PF *pfp; + struct map_element *mep; + + if (ele >= &curmap->map_element[curmap->map_num] || c < ele->k_base) { + if (ele > &curmap->map_element[0] && (funct != NULL || + (ele - 1)->k_prefmap == NULL)) + n1 = c - (ele - 1)->k_num; + else + n1 = HUGE; + if (ele < &curmap->map_element[curmap->map_num] && + (funct != NULL || ele->k_prefmap == NULL)) + n2 = ele->k_base - c; + else + n2 = HUGE; + if (n1 <= MAPELEDEF && n1 <= n2) { + ele--; + if ((pfp = calloc(c - ele->k_base + 1, + sizeof(PF))) == NULL) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + ewprintf("keymap too large"); + return (NULL); + } + if ((mp = malloc(sizeof(KEYMAP) + (curmap->map_max + (MAPGROW - 1)) * + sizeof(struct map_element))) == NULL) { + dobeep(); + 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. + */ + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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((unsigned char)*++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) { + dobeep(); + 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)); + } + dobeep(); + 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; + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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: + dobeep(); + 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 == '#')) + *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/file.c b/file.c @@ -0,0 +1,739 @@ +/* $OpenBSD: file.c,v 1.95 2014/04/09 20:50:03 florian Exp $ */ + +/* This file is in the public domain. */ + +/* + * File commands. + */ + +#include "def.h" + +#include <sys/stat.h> + +#include <libgen.h> + +size_t xdirname(char *, const char *, size_t); + +/* + * Insert a file into the current buffer. Real easy - just call the + * insertfile routine with the file name. + */ +/* ARGSUSED */ +int +fileinsert(int f, int n) +{ + char fname[NFILEN], *bufp, *adjf; + + if (getbufcwd(fname, sizeof(fname)) != TRUE) + fname[0] = '\0'; + bufp = eread("Insert file: ", fname, NFILEN, + EFNEW | EFCR | EFFILE | EFDEF); + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + adjf = adjustname(bufp, TRUE); + if (adjf == NULL) + return (FALSE); + return (insertfile(adjf, NULL, FALSE)); +} + +/* + * Select a file for editing. Look around to see if you can find the file + * in another buffer; if you can find it, just switch to the buffer. If + * you cannot find the file, create a new buffer, read in the text, and + * switch to the new buffer. + */ +/* ARGSUSED */ +int +filevisit(int f, int n) +{ + struct buffer *bp; + char fname[NFILEN], *bufp, *adjf; + int status; + + if (getbufcwd(fname, sizeof(fname)) != TRUE) + fname[0] = '\0'; + bufp = eread("Find file: ", fname, NFILEN, + EFNEW | EFCR | EFFILE | EFDEF); + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + adjf = adjustname(fname, TRUE); + if (adjf == NULL) + return (FALSE); + if ((bp = findbuffer(adjf)) == NULL) + return (FALSE); + curbp = bp; + if (showbuffer(bp, curwp, WFFULL) != TRUE) + return (FALSE); + if (bp->b_fname[0] == '\0') { + if ((status = readin(adjf)) != TRUE) + killbuffer(bp); + return (status); + } + return (TRUE); +} + +/* + * Replace the current file with an alternate one. Semantics for finding + * the replacement file are the same as 'filevisit', except the current + * buffer is killed before the switch. If the kill fails, or is aborted, + * revert to the original file. + */ +/* ARGSUSED */ +int +filevisitalt(int f, int n) +{ + struct buffer *bp; + char fname[NFILEN], *bufp, *adjf; + int status; + + if (getbufcwd(fname, sizeof(fname)) != TRUE) + fname[0] = '\0'; + bufp = eread("Find alternate file: ", fname, NFILEN, + EFNEW | EFCR | EFFILE | EFDEF); + if (bufp == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + + status = killbuffer(curbp); + if (status == ABORT || status == FALSE) + return (ABORT); + + adjf = adjustname(fname, TRUE); + if (adjf == NULL) + return (FALSE); + if ((bp = findbuffer(adjf)) == NULL) + return (FALSE); + curbp = bp; + if (showbuffer(bp, curwp, WFFULL) != TRUE) + return (FALSE); + if (bp->b_fname[0] == '\0') { + if ((status = readin(adjf)) != TRUE) + killbuffer(bp); + return (status); + } + return (TRUE); +} + +int +filevisitro(int f, int n) +{ + int error; + + error = filevisit(f, n); + if (error != TRUE) + return (error); + curbp->b_flag |= BFREADONLY; + return (TRUE); +} + +/* + * Pop to a file in the other window. Same as the last function, but uses + * popbuf instead of showbuffer. + */ +/* ARGSUSED */ +int +poptofile(int f, int n) +{ + struct buffer *bp; + struct mgwin *wp; + char fname[NFILEN], *adjf, *bufp; + int status; + + if (getbufcwd(fname, sizeof(fname)) != TRUE) + fname[0] = '\0'; + if ((bufp = eread("Find file in other window: ", fname, NFILEN, + EFNEW | EFCR | EFFILE | EFDEF)) == NULL) + return (ABORT); + else if (bufp[0] == '\0') + return (FALSE); + adjf = adjustname(fname, TRUE); + if (adjf == NULL) + return (FALSE); + if ((bp = findbuffer(adjf)) == NULL) + return (FALSE); + if (bp == curbp) + return (splitwind(f, n)); + if ((wp = popbuf(bp, WNONE)) == NULL) + return (FALSE); + curbp = bp; + curwp = wp; + if (bp->b_fname[0] == '\0') { + if ((status = readin(adjf)) != TRUE) + killbuffer(bp); + return (status); + } + return (TRUE); +} + +/* + * 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; + struct stat statbuf; + int status, i, ro = FALSE; + PF *ael; + char dp[NFILEN]; + + /* 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) { + dobeep(); + 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; + + /* + * Set the buffer READONLY flag if any of following are true: + * 1. file is a directory. + * 2. file is read-only. + * 3. file doesn't exist and directory is read-only. + */ + if (fisdir(fname) == TRUE) { + ro = TRUE; + } else if ((access(fname, W_OK) == -1)) { + if (errno != ENOENT) { + ro = TRUE; + } else if (errno == ENOENT) { + (void)xdirname(dp, fname, sizeof(dp)); + (void)strlcat(dp, "/", sizeof(dp)); + + /* Missing directory; keep buffer rw, like emacs */ + if (stat(dp, &statbuf) == -1 && errno == ENOENT) { + if (eyorn("Missing directory, create") == TRUE) + (void)do_makedir(dp); + } else if (access(dp, W_OK) == -1 && errno == EACCES) { + ewprintf("File not found and directory" + " write-protected"); + 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) { + dobeep(); + ewprintf("Cannot insert: file is a directory, %s", + fname); + goto cleanup; + } + killbuffer(bp); + bp = dired_(fname); + undo_enable(FFRAND, x); + if (bp == NULL) + return (FALSE); + curbp = bp; + return (showbuffer(bp, curwp, WFFULL | WFMODE)); + } else { + (void)xdirname(bp->b_cwd, fname, sizeof(bp->b_cwd)); + (void)strlcat(bp->b_cwd, "/", sizeof(bp->b_cwd)); + } + opos = curwp->w_doto; + oline = curwp->w_dotline; + /* + * Open a new line at dot and start inserting after it. + * We will delete this newline after insertion. + * Disable undo, as we create the undo record manually. + */ + x2 = undo_enable(FFRAND, 0); + (void)lnewline(); + olp = lback(curwp->w_dotp); + undo_enable(FFRAND, x2); + + nline = 0; + siz = 0; + while ((s = ffgetline(ffp, line, linesize, &nbytes)) != FIOERR) { +retry: + siz += nbytes + 1; + switch (s) { + case FIOSUC: + /* FALLTHRU */ + case FIOEOF: + ++nline; + if ((lp1 = lalloc(nbytes)) == NULL) { + /* keep message on the display */ + s = FIOERR; + undo_add_insert(olp, opos, + siz - nbytes - 1 - 1); + goto endoffile; + } + bcopy(line, &ltext(lp1)[0], nbytes); + lp2 = lback(curwp->w_dotp); + lp2->l_fp = lp1; + lp1->l_fp = curwp->w_dotp; + lp1->l_bp = lp2; + curwp->w_dotp->l_bp = lp1; + if (s == FIOEOF) { + undo_add_insert(olp, opos, siz - 1); + goto endoffile; + } + break; + case FIOLONG: { + /* a line too long to fit in our buffer */ + char *cp; + int newsize; + + newsize = linesize * 2; + if (newsize < 0 || + (cp = malloc(newsize)) == NULL) { + dobeep(); + 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: + dobeep(); + 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) { + if (S_ISDIR(statbuf.st_mode)) { + dobeep(); + ewprintf("%s is a directory", adjfname); + return (FALSE); + } + 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); + undo_add_boundary(FFRAND, 1); + undo_add_modified(); + } + 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') { + dobeep(); + 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); + undo_add_boundary(FFRAND, 1); + undo_add_modified(); + } + 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) +{ + struct stat statbuf; + int s; + char dp[NFILEN]; + + if (stat(fn, &statbuf) == -1 && errno == ENOENT) { + errno = 0; + (void)xdirname(dp, fn, sizeof(dp)); + (void)strlcat(dp, "/", sizeof(dp)); + if (access(dp, W_OK) && errno == EACCES) { + dobeep(); + ewprintf("Directory %s write-protected", dp); + return (FIOERR); + } else if (errno == ENOENT) { + dobeep(); + ewprintf("%s: no such directory", dp); + return (FIOERR); + } + } + /* 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); + dobeep(); + 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/fileio.c b/fileio.c @@ -0,0 +1,763 @@ +/* $OpenBSD: fileio.c,v 1.98 2014/11/16 04:16:41 guenther Exp $ */ + +/* This file is in the public domain. */ + +/* + * POSIX fileio.c + */ +#include "def.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/wait.h> + +#include <fcntl.h> +#include <limits.h> +#include <dirent.h> +#include <pwd.h> +#include <string.h> +#include <unistd.h> + +#include "kbd.h" +#include "pathnames.h" + +static char *bkuplocation(const char *); +static int bkupleavetmp(const char *); + +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; + dobeep(); + ewprintf("Cannot open file for writing : %s", strerror(errno)); + return (FIOERR); + } + + if ((*ffp = fdopen(fd, "w")) == NULL) { + dobeep(); + 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)) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + ewprintf("Can't stat %s : %s", fn, strerror(errno)); + return (FALSE); + } + + if ((bkpth = bkuplocation(fn)) == NULL) + return (FALSE); + + if (asprintf(&nname, "%s~", bkpth) == -1) { + dobeep(); + ewprintf("Can't allocate backup file name : %s", strerror(errno)); + free(bkpth); + return (ABORT); + } + if (asprintf(&tname, "%s.XXXXXXXXXX", bkpth) == -1) { + dobeep(); + 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 + * remove all occurrences of /./ and /../ + */ +char * +adjustname(const char *fn, int slashslash) +{ + static char fnb[PATH_MAX]; + 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) { + dobeep(); + 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)) { + dobeep(); + ewprintf("Path too long"); + return (NULL); + } + } + fn = cp; + if (*fn == '/') + fn++; + } + if (strlcat(path, fn, sizeof(path)) >= sizeof(path)) { + dobeep(); + ewprintf("Path too long"); + return (NULL); + } + if ((ret = strndup(path, NFILEN)) == NULL) + return (NULL); + + return (ret); +} diff --git a/funmap.c b/funmap.c @@ -0,0 +1,290 @@ +/* $OpenBSD: funmap.c,v 1.47 2013/05/31 18:03:44 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",}, + {toggleaudiblebell, "audible-bell",}, + {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",}, + {colnotoggle, "column-number-mode",}, + {copyregion, "copy-region-as-kill",}, +#ifdef REGEX + {cntmatchlines, "count-matches",}, + {cntnonmatchlines, "count-non-matches",}, +#endif /* REGEX */ + {cscreatelist, "cscope-create-list-of-files-to-index",}, + {csfuncalled, "cscope-find-called-functions",}, + {csegrep, "cscope-find-egrep-pattern",}, + {csfindinc, "cscope-find-files-including-file",}, + {cscallerfuncs, "cscope-find-functions-calling-this-function",}, + {csdefinition, "cscope-find-global-definition",}, + {csfindfile, "cscope-find-this-file",}, + {cssymbol, "cscope-find-this-symbol",}, + {csfindtext, "cscope-find-this-text-string",}, + {csnextfile, "cscope-next-file",}, + {csnextmatch, "cscope-next-symbol",}, + {csprevfile, "cscope-prev-file",}, + {csprevmatch, "cscope-prev-symbol",}, + {redefine_key, "define-key",}, + {backdel, "delete-backward-char",}, + {deblank, "delete-blank-lines",}, + {forwdel, "delete-char",}, + {delwhite, "delete-horizontal-space",}, + {delleadwhite, "delete-leading-space",}, +#ifdef REGEX + {delmatchlines, "delete-matching-lines",}, + {delnonmatchlines, "delete-non-matching-lines",}, +#endif /* REGEX */ + {onlywind, "delete-other-windows",}, + {deltrailwhite, "delete-trailing-space",}, + {delwind, "delete-window",}, + {wallchart, "describe-bindings",}, + {desckey, "describe-key-briefly",}, + {diffbuffer, "diff-buffer-with-file",}, + {digit_argument, "digit-argument",}, + {lowerregion, "downcase-region",}, + {lowerword, "downcase-word",}, + {showversion, "emacs-version",}, + {finishmacro, "end-kbd-macro",}, + {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",}, + {filevisitalt, "find-alternate-file",}, + {filevisit, "find-file",}, + {poptofile, "find-file-other-window",}, + {filevisitro, "find-file-read-only",}, + {findtag, "find-tag",}, + {forwchar, "forward-char",}, + {gotoeop, "forward-paragraph",}, + {forwword, "forward-word",}, + {bindtokey, "global-set-key",}, + {unbindtokey, "global-unset-key",}, + {globalwdtoggle, "global-wd-mode",}, + {gotoline, "goto-line",}, + {help_help, "help-help",}, + {indent, "indent-current-line",}, + {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",}, + {makedir, "make-directory",}, + {markbuffer, "mark-whole-buffer",}, + {do_meta, "meta-key-mode",}, /* better name, anyone? */ + {negative_argument, "negative-argument",}, + {newline, "newline",}, + {lfindent, "newline-and-indent",}, + {forwline, "next-line",}, +#ifdef NOTAB + {notabmode, "no-tab-mode",}, +#endif /* NOTAB */ + {notmodified, "not-modified",}, + {openline, "open-line",}, + {nextwind, "other-window",}, + {overwrite_mode, "overwrite-mode",}, + {poptag, "pop-tag-mark",}, + {prefixregion, "prefix-region",}, + {backline, "previous-line",}, + {prevwind, "previous-window",}, + {spawncli, "push-shell",}, + {showcwdir, "pwd",}, + {queryrepl, "query-replace",}, +#ifdef REGEX + {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",}, +#ifdef REGEX + {replstr, "replace-string",}, +#endif /* REGEX */ + {revertbuffer, "revert-buffer",}, + {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",}, + {shellcommand, "shell-command",}, + {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_add_boundary, "undo-boundary",}, + {undo_boundary_enable, "undo-boundary-toggle",}, + {undo_enable, "undo-enable",}, + {undo_dump, "undo-list",}, + {universal_argument, "universal-argument",}, + {upperregion, "upcase-region",}, + {upperword, "upcase-word",}, + {togglevisiblebell, "visible-bell",}, + {tagsvisit, "visit-tags-table",}, + {showcpos, "what-cursor-position",}, + {filewrite, "write-file",}, + {yank, "yank",}, + {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/funmap.h b/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/grep.c b/grep.c @@ -0,0 +1,347 @@ +/* $OpenBSD: grep.c,v 1.42 2014/11/16 04:16:41 guenther Exp $ */ + +/* This file is in the public domain */ + +#include "def.h" +#include "kbd.h" +#include "funmap.h" + +#include <sys/types.h> +#include <ctype.h> +#include <libgen.h> +#include <limits.h> +#include <time.h> + +int globalwd = FALSE; +static int compile_goto_error(int, int); +int next_error(int, int); +static int grep(int, int); +static int gid(int, int); +static struct buffer *compile_mode(const char *, const char *); +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(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 */ +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], *bufp; + int c; + 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) { + dobeep(); + ewprintf("Can't change dir to %s", bp->b_cwd); + return (NULL); + } + if ((fpipe = popen(qcmd, "r")) == NULL) { + dobeep(); + 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) { + dobeep(); + 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; + } + dobeep(); + ewprintf("No more hits"); + return (FALSE); +} + +/* ARGSUSED */ +int +next_error(int f, int n) +{ + if (compile_win == NULL || compile_buffer == NULL) { + dobeep(); + ewprintf("No compilation active"); + return (FALSE); + } + curwp = compile_win; + curbp = compile_buffer; + if (curwp->w_dotp == blastlp(curbp)) { + dobeep(); + 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/help.c b/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/kbd.c b/kbd.c @@ -0,0 +1,436 @@ +/* $OpenBSD: kbd.c,v 1.26 2013/05/31 18:03:44 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(CMODE); + 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/kbd.h b/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/key.h b/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/keymap.c b/keymap.c @@ -0,0 +1,574 @@ +/* $OpenBSD: keymap.c,v 1.52 2014/08/14 12:22:58 bcallah 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 metaspex[] = { + justone, /* space */ + shellcommand /* ! */ +}; + +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 + }, + { + ' ', '!', metaspex, 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 (8 + NFUND_XMAPS + IMAPEXT) fundmap = { + 8 + NFUND_XMAPS, + 8 + 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 + }, + { + ']', ']', fund_cb, NULL + }, + { + '}', '}', fund_cb, NULL + }, + { + CCHR('?'), CCHR('?'), fund_del, NULL + }, +#ifdef FUND_XMAPS + FUND_XMAPS, +#endif /* FUND_XMAPS */ + } +}; + +static PF fill_sp[] = { + fillword /* ' ' */ +}; + +static struct KEYMAPE (1 + IMAPEXT) fillmap = { + 1, + 1 + IMAPEXT, + rescan, + { + { ' ', ' ', fill_sp, NULL } + } +}; + +static PF indent_lf[] = { + newline, /* ^J */ + rescan, /* ^K */ + rescan, /* ^L */ + lfindent /* ^M */ +}; + +static struct KEYMAPE (1 + IMAPEXT) indntmap = { + 1, + 1 + IMAPEXT, + rescan, + { + { + CCHR('J'), CCHR('M'), indent_lf, NULL + } + } +}; + +#ifdef NOTAB +static PF notab_tab[] = { + space_to_tabstop /* ^I */ +}; + +static struct KEYMAPE (1 + IMAPEXT) notabmap = { + 1, + 1 + IMAPEXT, + rescan, + { + { + CCHR('I'), CCHR('I'), notab_tab, NULL + } + } +}; +#endif /* NOTAB */ + +static struct KEYMAPE (1 + IMAPEXT) overwmap = { + 0, + 1 + IMAPEXT, /* 1 to avoid 0 sized array */ + rescan, + { + /* unused dummy entry for VMS C */ + { + (KCHAR)0, (KCHAR)0, NULL, NULL + } + } +}; + + +/* + * The basic (root) keyboard map + */ +struct maps_s fundamental_mode = { (KEYMAP *)&fundmap, "fundamental" }; + +/* + * give names to the maps, for use by help etc. If the map is to be bindable, + * it must also be listed in the function name table below with the same + * name. Maps created dynamically currently don't get added here, thus are + * unnamed. Modes are just named keymaps with functions to add/subtract them + * from a buffer's list of modes. If you change a mode name, change it in + * modes.c also. + */ + +static struct maps_s map_table[] = { + {(KEYMAP *) &fillmap, "fill",}, + {(KEYMAP *) &indntmap, "indent",}, +#ifdef NOTAB + {(KEYMAP *) &notabmap, "notab",}, +#endif /* NOTAB */ + {(KEYMAP *) &overwmap, "overwrite",}, + {(KEYMAP *) &metamap, "esc prefix",}, + {(KEYMAP *) &cXmap, "c-x prefix",}, + {(KEYMAP *) &cX4map, "c-x 4 prefix",}, + {(KEYMAP *) &helpmap, "help",}, + {NULL, NULL} +}; + +struct maps_s *maps; + +void +maps_init(void) +{ + int i; + struct maps_s *mp; + + maps = &fundamental_mode; + for (i = 0; map_table[i].p_name != NULL; i++) { + mp = &map_table[i]; + mp->p_next = maps; + maps = mp; + } +} + +/* + * Insert a new (named) keymap at the head of the keymap list. + */ +int +maps_add(KEYMAP *map, const char *name) +{ + struct maps_s *mp; + + if ((mp = malloc(sizeof(*mp))) == NULL) + return (FALSE); + + mp->p_name = name; + mp->p_map = map; + mp->p_next = maps; + maps = mp; + + return (TRUE); +} + +struct maps_s * +name_mode(const char *name) +{ + struct maps_s *mp; + + for (mp = maps; mp != NULL; mp = mp->p_next) + if (strcmp(mp->p_name, name) == 0) + return (mp); + return (NULL); +} + +KEYMAP * +name_map(const char *name) +{ + struct maps_s *mp; + + return ((mp = name_mode(name)) == NULL ? NULL : mp->p_map); +} diff --git a/line.c b/line.c @@ -0,0 +1,648 @@ +/* $OpenBSD: line.c,v 1.54 2014/11/16 04:16:41 guenther 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. + * + * 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 <limits.h> +#include <stdlib.h> +#include <string.h> + +/* + * Allocate a new line of size `used'. lrealloc() can be called if the line + * ever needs to grow beyond that. + */ +struct line * +lalloc(int used) +{ + struct line *lp; + + if ((lp = malloc(sizeof(*lp))) == NULL) + return (NULL); + lp->l_text = NULL; + lp->l_size = 0; + lp->l_used = used; /* XXX */ + if (lrealloc(lp, used) == FALSE) { + free(lp); + return (NULL); + } + return (lp); +} + +int +lrealloc(struct line *lp, int newsize) +{ + char *tmp; + + if (lp->l_size < newsize) { + if ((tmp = realloc(lp->l_text, newsize)) == NULL) + return (FALSE); + lp->l_text = tmp; + lp->l_size = newsize; + } + return (TRUE); +} + +/* + * Delete line "lp". Fix all of the links that might point to it (they are + * moved to offset 0 of the next line. Unlink the line from whatever buffer + * it might be in, and release the memory. The buffers are updated too; the + * magic conditions described in the above comments don't hold here. + */ +void +lfree(struct line *lp) +{ + struct buffer *bp; + struct mgwin *wp; + + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_linep == lp) + wp->w_linep = lp->l_fp; + if (wp->w_dotp == lp) { + wp->w_dotp = lp->l_fp; + wp->w_doto = 0; + } + if (wp->w_markp == lp) { + wp->w_markp = lp->l_fp; + wp->w_marko = 0; + } + } + for (bp = bheadp; bp != NULL; bp = bp->b_bufp) { + if (bp->b_nwnd == 0) { + if (bp->b_dotp == lp) { + bp->b_dotp = lp->l_fp; + bp->b_doto = 0; + } + if (bp->b_markp == lp) { + bp->b_markp = lp->l_fp; + bp->b_marko = 0; + } + } + } + lp->l_bp->l_fp = lp->l_fp; + lp->l_fp->l_bp = lp->l_bp; + if (lp->l_text != NULL) + free(lp->l_text); + free(lp); +} + +/* + * This routine is called when a character changes in place in the current + * buffer. It updates all of the required flags in the buffer and window + * system. The flag used is passed as an argument; if the buffer is being + * displayed in more than 1 window we change EDIT to HARD. Set MODE if the + * mode line needs to be updated (the "*" has to be set). + */ +void +lchange(int flag) +{ + struct mgwin *wp; + + /* update mode lines if this is the first change. */ + if ((curbp->b_flag & BFCHG) == 0) { + flag |= WFMODE; + curbp->b_flag |= BFCHG; + } + for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { + if (wp->w_bufp == curbp) { + wp->w_rflag |= flag; + if (wp != curwp) + wp->w_rflag |= WFFULL; + } + } +} + +/* + * Insert "n" bytes from "s" at the current location of dot. + * In the easy case all that happens is the text is stored in the line. + * In the hard case, the line has to be reallocated. When the window list + * is updated, take special care; I screwed it up once. You always update + * dot in the current window. You update mark and a dot in another window + * if it is greater than the place where you did the insert. Return TRUE + * if all is well, and FALSE on errors. + */ +int +linsert_str(const char *s, int n) +{ + struct line *lp1; + struct mgwin *wp; + RSIZE i; + int doto, k; + + if ((k = checkdirty(curbp)) != TRUE) + return (k); + + if (curbp->b_flag & BFREADONLY) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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; + struct mgwin *wp; + int nlen, tcurwpdotline; + + 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++; + + tcurwpdotline = 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; + if (wp->w_dotline >= tcurwpdotline) + wp->w_dotline++; + } + 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; + wp->w_dotline++; + } else if (wp->w_dotline > tcurwpdotline) + wp->w_dotline++; + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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) { + dobeep(); + 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/macro.c b/macro.c @@ -0,0 +1,107 @@ +/* $OpenBSD: macro.c,v 1.15 2014/03/20 07:47:29 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)) { + dobeep(); + 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/macro.h b/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/main.c b/main.c @@ -0,0 +1,253 @@ +/* $OpenBSD: main.c,v 1.74 2014/11/16 04:16:41 guenther Exp $ */ + +/* This file is in the public domain. */ + +/* + * Mainline. + */ + +#include "def.h" +#include "kbd.h" +#include "funmap.h" +#include "macro.h" + +#include <err.h> +#include <limits.h> +#include <locale.h> + +int thisflag; /* flags, this command */ +int lastflag; /* flags, last command */ +int curgoal; /* goal column */ +int startrow; /* row to start */ +int doaudiblebell; /* audible bell toggle */ +int dovisiblebell; /* visible bell toggle */ +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; + + setlocale(LC_CTYPE, ""); + + 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. */ + bellinit(); /* Audible and visible bell. */ + + /* + * 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(CMODE); + + /* 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(CMODE); + lastflag = thisflag; + thisflag = 0; + + switch (doin()) { + case TRUE: + break; + case ABORT: + ewprintf("Quit"); + /* FALLTHRU */ + case FALSE: + default: + 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 == FIOERR || s == UERROR) + return (FALSE); + if (s == FALSE + || eyesno("Modified buffers exist; really exit") == TRUE) { + vttidy(); + closetags(); + exit(0); + } + 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/match.c b/match.c @@ -0,0 +1,185 @@ +/* $OpenBSD: match.c,v 1.17 2013/05/31 18:03:44 lum 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) + dobeep(); + } + 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(CMODE); /* 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(CMODE); + } 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.1 b/mg.1 @@ -0,0 +1,1067 @@ +.\" $OpenBSD: mg.1,v 1.89 2014/11/03 14:53:22 jmc Exp $ +.\" This file is in the public domain. +.\" +.Dd $Mdocdate: November 3 2014 $ +.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 file is changed outside +.Nm +and its buffer is about to be changed, +.Nm +prompts if the change should go ahead (y), not go ahead (n) or if the buffer +should be reverted (r) to the latest file on disk. +.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-! +shell-command +.It M-. +find-tag +.It M-* +pop-tag-mark +.It M-% +query-replace +.It M-< +beginning-of-buffer +.It M-> +end-of-buffer +.It M-\e +delete-horizontal-space +.It M-^ +join-line +.It M-b +backward-word +.It M-c +capitalize-word +.It M-d +kill-word +.It M-f +forward-word +.It M-l +downcase-word +.It M-m +back-to-indentation +.It M-q +fill-paragraph +.It M-r +search-backward +.It M-s +search-forward +.It M-u +upcase-word +.It M-v +scroll-down +.It M-w +copy-region-as-kill +.It M-x +execute-extended-command +.It M-{ +backward-paragraph +.It M-| +shell-command-on-region +.It M-} +forward-paragraph +.It M-~ +not-modified +.It M-DEL +backward-kill-word +.It C-_ +undo +.It ) +blink-and-insert +.It DEL +delete-backward-char +.El +.Pp +For a complete description of +.Nm +commands, see +.Sx MG COMMANDS . +To see the active keybindings at any time, type +.Dq M-x describe-bindings . +.Sh MG COMMANDS +Commands are invoked by +.Dq M-x , +or by binding to a key. +Many commands take an optional numerical parameter, +.Va n . +This parameter is set either by +M-<n> (where +.Va n +is the numerical argument) before the command, or by +one or more invocations of the universal argument, usually bound to C-u. +When invoked in this manner, the value of the numeric parameter to +be passed is displayed in the minibuffer before the M-x. +One common use of the parameter is in mode toggles (e.g.\& +make-backup-files). +If no parameter is supplied, the mode is toggled to its +alternate state. +If a positive parameter is supplied, the mode is forced to on. +Otherwise, it is forced to off. +.\" +.Bl -tag -width xxxxx +.It apropos +Help Apropos. +Prompt the user for a string, open the *help* buffer, +and list all +.Nm +commands that contain that string. +.It audible-bell +Toggle the audible system bell. +.It auto-execute +Register an auto-execute hook; that is, specify a filename pattern +(conforming to the shell's filename globbing rules) and an associated +function to execute when a file matching the specified pattern +is read into a buffer. +.It auto-fill-mode +Toggle auto-fill mode (sometimes called mail-mode), +where text inserted past the fill column is automatically wrapped +to a new line. +.It auto-indent-mode +Toggle indent mode, where indentation is preserved after a newline. +.It back-to-indentation +Move the dot to the first non-whitespace character on the current line. +.It backup-to-home-directory +Save backup copies to a +.Pa ~/.mg.d +directory instead of working directory. +Requires make-backup-files to be on. +.It backward-char +Move cursor backwards one character. +.It backward-kill-word +Kill text backwards by +.Va n +words. +.It backward-paragraph +Move cursor backwards +.Va n +paragraphs. +Paragraphs are delimited by <NL><NL> or <NL><TAB> or <NL><SPACE>. +.It backward-word +Move cursor backwards by the specified number of words. +.It beginning-of-buffer +Move cursor to the top of the buffer. +.It beginning-of-line +Move cursor to the beginning of the line. +.It blink-and-insert +Self-insert a character, then search backwards and blink its +matching delimiter. +For delimiters other than +parenthesis, brackets, and braces, the character itself +is used as its own match. +.It bsmap-mode +Toggle bsmap mode, where DEL and C-h are swapped. +.It c-mode +Toggle a KNF-compliant mode for editing C program files. +.It call-last-kbd-macro +Invoke the keyboard macro. +.It capitalize-word +Capitalize +.Va n +words; i.e. convert the first character of the word to +upper case, and subsequent letters to lower case. +.It cd +Change the global working directory. +See also global-wd-mode. +.It column-number-mode +Toggle whether the column number is displayed in the modeline. +.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 diff-buffer-with-file +View the differences between buffer and its associated file. +.It digit-argument +Process a numerical argument for keyboard-invoked functions. +.It downcase-region +Set all characters in the region to lower case. +.It downcase-word +Set characters to lower case, starting at the dot, and ending +.Va n +words away. +.It emacs-version +Return an +.Nm +version string. +.It end-kbd-macro +Stop defining a keyboard macro. +.It end-of-buffer +Move cursor to the end of the buffer. +.It end-of-line +Move cursor to the end of the line. +.It enlarge-window +Enlarge the current window by shrinking either the window above +or below it. +.It eval-current-buffer +Evaluate the current buffer as a series of +.Nm +commands. +Useful for testing +.Nm +startup files. +.It eval-expression +Get one line from the user, and run it. +Useful for testing expressions in +.Nm +startup files. +.It exchange-point-and-mark +Swap the values of "dot" and "mark" in the current window. +Return an error if no mark is set. +.It execute-extended-command +Invoke an extended command; i.e. M-x. +Call the message line routine to read in the command name and apply +autocompletion to it. +When it comes back, look the name up in the symbol table and run the +command if it is found, passing arguments as necessary. +Print an error if there is anything wrong. +.It fill-paragraph +Justify a paragraph, wrapping text at the current fill column. +.It find-file +Select a file for editing. +First check if the file can be found +in another buffer; if it is there, just switch to that buffer. +If the file cannot be found, create a new buffer, read in the +file from disk, and switch to the new buffer. +.It find-file-read-only +Same as find-file, except the new buffer is set to read-only. +.It find-alternate-file +Replace the current file with an alternate one. +Semantics for finding the replacement file are the same as +find-file, except the current buffer is killed before the switch. +If the kill fails, or is aborted, revert to the original file. +.It find-file-other-window +Opens the specified file in a second buffer. +Splits the current window if necessary. +.It find-tag +Jump to definition of tag at dot. +.It forward-char +Move cursor forwards (or backwards, if +.Va n +is negative) +.Va n +characters. +Returns an error if the end of buffer is reached. +.It forward-paragraph +Move forward +.Va n +paragraphs. +Paragraphs are delimited by <NL><NL> or <NL><TAB> or <NL><SPACE>. +.It forward-word +Move the cursor forward by the specified number of words. +.It global-set-key +Bind a key in the global (fundamental) key map. +.It global-unset-key +Unbind a key from the global (fundamental) key map; i.e. set it to 'rescan'. +.It global-wd-mode +Toggle global working-directory mode. +When enabled, +.Nm +defaults to opening files (and executing commands like compile and grep) +relative to the global working directory. +When disabled, a working directory is set for each buffer. +.It goto-line +Go to a specific line. +If an argument is present, then +it is the line number, else prompt for a line number to use. +.It help-help +Prompts for one of (a)propos, (b)indings, des(c)ribe key briefly. +.It insert +Insert a string, mainly for use from macros. +.It insert-buffer +Insert the contents of another buffer at dot. +.It insert-file +Insert a file into the current buffer at dot. +.It insert-with-wrap +Insert the bound character with word wrap. +Check to see if we're past the fill column, and if so, +justify this line. +.It isearch-backward +Use incremental searching, initially in the reverse direction. +isearch ignores any explicit arguments. +If invoked during macro definition or evaluation, the non-incremental +search-backward is invoked instead. +.It isearch-forward +Use incremental searching, initially in the forward direction. +isearch ignores any explicit arguments. +If invoked during macro definition or evaluation, the non-incremental +search-forward is invoked instead. +.It join-line +Join the current line to the previous. +If called with an argument, +join the next line to the current one. +.It just-one-space +Delete any whitespace around dot, then insert a space. +.It keyboard-quit +Abort the current action. +.It kill-buffer +Dispose of a buffer, by name. +If the buffer name does not start and end with an asterisk, +prompt the user if the buffer +has been changed. +.It kill-line +Kill line. +If called without an argument, it kills from dot to the end +of the line, unless it is at the end of the line, when it kills the +newline. +If called with an argument of 0, it kills from the start of the +line to dot. +If called with a positive argument, it kills from dot +forward over that number of newlines. +If called with a negative argument +it kills any text before dot on the current line, then it kills back +abs(n) lines. +.It kill-paragraph +Delete +.Va n +paragraphs starting with the current one. +.It kill-region +Kill the currently defined region. +.It kill-word +Delete forward +.Va n +words. +.It leave-tmpdir-backups +Modifies the behaviour of backup-to-home-directory. +Backup files that would normally reside in the system +.Ev TMPDIR +are left there and not moved to the +.Pa ~/.mg.d +directory. +.It line-number-mode +Toggle whether the line number is 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 make-directory +Prompt the user for a path or directory name which is then created. +.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 revert-buffer +Revert the current buffer to the latest file on disk. +.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 +Execute external command from mini-buffer. +.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 in front of and under dot, +then move forward one character. +Treat newline characters the same as any other. +.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 visible-bell +Toggle the visible bell. +If this toggle is on, the modeline will flash. +.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