diff --git a/usr.bin/more/Makefile b/usr.bin/more/Makefile index 79048432118c..a9f7d547d9ec 100644 --- a/usr.bin/more/Makefile +++ b/usr.bin/more/Makefile @@ -1,16 +1,34 @@ # From: @(#)Makefile 8.1 (Berkeley) 6/6/93 # $FreeBSD$ +# PROG= more -CFLAGS+=-I${.CURDIR} -DTERMIOS -SRCS= ch.c command.c decode.c help.c input.c line.c linenum.c main.c \ - option.c os.c output.c position.c prim.c screen.c signal.c tags.c \ - ttyin.c +CFLAGS+=-I${.CURDIR} -I${.OBJDIR} -DTERMIOS +SRCS= ch.c command.c defrc.h help.c input.c line.c linenum.c macro.c main.c \ + ncommand.c option.c os.c output.c position.c prim.c screen.c signal.c \ + tags.c ttyin.c DPADD= ${LIBTERMCAP} LDADD= -ltermcap +CLEANFILES+= defrc.h + +EXAMPDIR= /usr/share/examples/more +EXAMPLES= default.morerc less.morerc most.morerc + +defrc.h: default.morerc + @${ECHO} '/* ${.TARGET:T} auto-generated from ${.ALLSRC:T} */' \ + > ${.TARGET} + @${ECHO} '#define DEFRC "\' >> ${.TARGET} + sed -e 's/\\/\\\\/g' -e 's/\"/\\\"/g' -e 's/$$/\\n\\/' \ + < ${.ALLSRC} >> ${.TARGET} + @${ECHO} \" >> ${.TARGET} + beforeinstall: - ${INSTALL} -c -o ${BINOWN} -g ${BINGRP} -m 444 ${.CURDIR}/more.help \ - ${DESTDIR}/usr/share/misc + ${INSTALL} ${COPY} -o ${SHAREOWN} -g ${SHAREGRP} -m ${SHAREMODE} \ + ${.CURDIR}/more.help ${DESTDIR}/usr/share/misc +.for xzamp in ${EXAMPLES} + ${INSTALL} ${COPY} -o ${SHAREOWN} -g ${SHAREGRP} -m ${SHAREMODE} \ + ${.CURDIR}/${xzamp} ${DESTDIR}${EXAMPDIR}/${xzamp} +.endfor .include diff --git a/usr.bin/more/command.c b/usr.bin/more/command.c index 0a400d881bc6..92b860af262a 100644 --- a/usr.bin/more/command.c +++ b/usr.bin/more/command.c @@ -1,5 +1,6 @@ /* * Copyright (c) 1988 Mark Nudleman + * Portions copyright (c) 1999 T. Michael Vanderhoek * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. * @@ -41,114 +42,145 @@ static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ +/* + * Functions for interacting with the user directly printing hello + * messages or reading from the terminal. All of these functions deal + * specifically with the prompt line, and only the prompt line. + */ + #include +#include #include +#include #include #include #include "less.h" #include "pathnames.h" -#define NO_MCA 0 -#define MCA_DONE 1 -#define MCA_MORE 2 - extern int erase_char, kill_char, werase_char; -extern int ispipe; extern int sigs; extern int quit_at_eof; extern int hit_eof; -extern int sc_width; -extern int sc_height; -extern int sc_window; extern int horiz_off; +extern int sc_width; +extern int bo_width; +extern int be_width; +extern int so_width; +extern int se_width; extern int curr_ac; extern int ac; extern char **av; -extern int scroll; extern int screen_trashed; /* The screen has been overwritten */ -static char cmdbuf[120]; /* Buffer for holding a multi-char command */ -static char *cp; /* Pointer into cmdbuf */ -static int cmd_col; /* Current column of the multi-char command */ -static int longprompt; /* if stat command instead of prompt */ -static int mca; /* The multicharacter command (action) */ -static int last_mca; /* The previous mca */ -static int number; /* The number typed by the user */ -static int wsearch; /* Search for matches (1) or non-matches (0) */ +static int cmd_col; /* Current screen column when accepting input */ -#define CMD_RESET cp = cmdbuf /* reset command buffer to empty */ -#define CMD_EXEC lower_left(); flush() +static cmd_char(), cmd_erase(), getcc(); -/* backspace in command buffer. */ -static -cmd_erase() + +/***************************************************************************** + * + * Functions for reading-in user input. + * + */ + +static int biggetinputhack_f; + +/* biggetinputhack() + * + * Performs as advertised. + */ +biggetinputhack() { - int c; - /* - * backspace past beginning of the string: this usually means - * abort the command. - */ - if (cp == cmdbuf) - return(1); - - /* erase an extra character, for the carat. */ - c = *--cp; - if (CONTROL_CHAR(c)) { - backspace(); - --cmd_col; - } - - backspace(); - --cmd_col; - return(0); -} - -/* set up the display to start a new multi-character command. */ -start_mca(action, prompt) - int action; - char *prompt; -{ - lower_left(); - clear_eol(); - putstr(prompt); - cmd_col = strlen(prompt); - mca = action; + biggetinputhack_f = 1; } /* - * process a single character of a multi-character command, such as - * a number, or the pattern of a search command. + * Read a line of input from the terminal. Reads at most bufsiz - 1 characters + * and places them in buffer buf. They are NUL-terminated. Prints the + * temporary prompt prompt. */ -static -cmd_char(c) +getinput(prompt, buf, bufsiz) + const char *prompt; + char *buf; + int bufsiz; +{ + extern bo_width, be_width; + char *bufcur; int c; + + prmpt(prompt); + + bufcur = buf; + for (;;) { + c = getcc(); + if (c == '\n') { + *bufcur = '\0'; + return; + } + if (c == READ_INTR || + cmd_char(c, buf, &bufcur, buf + bufsiz - 1)) { + /* input cancelled */ + if (bufsiz) *buf = '\0'; + return; + } + if (biggetinputhack_f) { + biggetinputhack_f = 0; + *bufcur = '\0'; + return; + } + } +} + +/* + * Process a single character of a multi-character input, such as + * a number, or the pattern of a search command. Returns true if the user + * has cancelled the multi-character input, false otherwise and attempts + * to add it to buf (not exceeding bufsize). Prints the character on the + * terminal output. The bufcur should initially equal bufbeg. After that + * it does not need to be touched or modified by the user, but may be expected + * to point at the future position of the next character. + */ +static int +cmd_char(c, bufbeg, bufcur, bufend) + int c; /* The character to process */ + char *bufbeg; /* The buffer to add the character to */ + char **bufcur; /* The position at which to add the character */ + char *bufend; /* The last spot available in the buffer --- remember + * to leave one after bufend for the '\0'! (You must + * add the '\0' yourself!!) */ { if (c == erase_char) - return(cmd_erase()); + return(cmd_erase(bufbeg, bufcur)); /* in this order, in case werase == erase_char */ if (c == werase_char) { - if (cp > cmdbuf) { - while (isspace(cp[-1]) && !cmd_erase()); - while (!isspace(cp[-1]) && !cmd_erase()); - while (isspace(cp[-1]) && !cmd_erase()); + if (*bufcur > bufbeg) { + while (isspace((*bufcur)[-1]) && + !cmd_erase(bufbeg, bufcur)) ; + while (!isspace((*bufcur)[-1]) && + !cmd_erase(bufbeg, bufcur)) ; + while (isspace((*bufcur)[-1]) && + !cmd_erase(bufbeg, bufcur)) ; } - return(cp == cmdbuf); + return *bufcur == bufbeg; } if (c == kill_char) { - while (!cmd_erase()); - return(1); + while (!cmd_erase(bufbeg, bufcur)); + return 1; } + /* * No room in the command buffer, or no room on the screen; - * {{ Could get fancy here; maybe shift the displayed line - * and make room for more chars, like ksh. }} + * XXX If there is no room on the screen, we should just let the + * screen scroll down and set screen_trashed=1 appropriately, or + * alternatively, scroll the prompt line horizontally. */ - if (cp >= &cmdbuf[sizeof(cmdbuf)-1] || cmd_col >= sc_width-3) + assert (*bufcur <= bufend); + if (*bufcur == bufend || cmd_col >= sc_width - 3) bell(); else { - *cp++ = c; + *(*bufcur)++ = c; if (CONTROL_CHAR(c)) { putchr('^'); cmd_col++; @@ -158,12 +190,123 @@ cmd_char(c) putchr(c); cmd_col++; } - return(0); + return 0; } +/* + * Helper function to cmd_char(). Backs-up one character from bufcur in the + * buffer passed, and prints a backspace on the screen. Returns true if the + * we backspaced past bufbegin (ie. the input is being aborted), and false + * otherwise. The bufcur is expected to point to the future location of the + * next character in the buffer, and is modified appropriately. + */ +static +cmd_erase(bufbegin, bufcur) + char *bufbegin; + char **bufcur; +{ + int c; + + /* + * XXX Could add code to detect a backspace that is backing us over + * the beginning of a line and onto the previous line. The backspace + * would not be printed for some terminals (eg. hardcopy) in that + * case. + */ + + /* + * backspace past beginning of the string: this usually means + * abort the input. + */ + if (*bufcur == bufbegin) + return 1; + + (*bufcur)--; + + /* If erasing a control-char, erase an extra character for the carat. */ + c = **bufcur; + if (CONTROL_CHAR(c)) { + backspace(); + cmd_col--; + } + + backspace(); + cmd_col--; + + return 0; +} + +static int ungotcc; + +/* + * Get command character from the terminal. + */ +static +getcc() +{ + int ch; + off_t position(); + + /* left over from error() routine. */ + if (ungotcc) { + ch = ungotcc; + ungotcc = 0; + return(ch); + } + + return(getchr()); +} + +/* + * Same as ungetc(), but works for people who don't like to use streams. + */ +ungetcc(c) + int c; +{ + ungotcc = c; +} + + +/***************************************************************************** + * + * prompts + * + */ + +static int longprompt; + +/* + * Prints prmpt where the prompt would normally appear. This is different + * from changing the current prompt --- this is more like printing a + * unimportant notice or error. The prmpt line will be printed in bold (if + * possible). Will in the future print only the last sc_width - 1 - bo_width + * characters (to prevent newline). + */ +prmpt(prmpt) + const char *prmpt; +{ + lower_left(); + clear_eol(); + bo_enter(); + putxstr(prmpt); + bo_exit(); + flush(); + cmd_col = strlen(prmpt) + bo_width + be_width; +} + +/* + * Print the main prompt that signals we are ready for user commands. This + * also magically positions the current file where it should be (either by + * calling repaint() if screen_trashed or by searching for a search + * string that was specified through option.c on the more(1) command line). + * Additional magic will randomly call the quit() function. + * + * This is really intended to do a lot of the work of commands(). It has + * little purpose outside of commands(). + */ prompt() { - extern int linenums, short_file; + extern int linenums, short_file, ispipe; extern char *current_name, *firstsearch, *next_name; off_t len, pos, ch_length(), position(), forw_line(); char pbuf[40]; @@ -173,8 +316,13 @@ prompt() * if search string provided, go there instead. */ if (position(TOP) == NULL_POSITION) { +#if 0 +/* This code causes "more zero-byte-file /etc/termcap" to skip straight + * to the /etc/termcap file ... that is undesireable. There are only a few + * instances where these two lines perform something useful. */ if (forw_line((off_t)0) == NULL_POSITION) - return(0); + return 0 ; +#endif if (!firstsearch || !search(1, firstsearch, 1, 1)) jump_back(1); } @@ -188,38 +336,42 @@ prompt() /* select the proper prompt and display it. */ lower_left(); clear_eol(); + pbuf[sizeof(pbuf) - 1] = '\0'; if (longprompt) { /* * Get the current line/pos from the BOTTOM of the screen * even though that's potentially confusing for the user - * when switching between NO_HORIZ_OFF and a valid horiz_off. - * In exchange, it is sometimes easier for the user to tell - * when a file is relatively short vs. long. + * when switching between wraplines=true and a valid horiz_off + * (with wraplines=false). In exchange, it is sometimes + * easier for the user to tell when a file is relatively + * short vs. long. */ so_enter(); putstr(current_name); putstr(":"); if (!ispipe) { - (void)snprintf(pbuf, sizeof(pbuf), + (void)snprintf(pbuf, sizeof(pbuf) - 1, " file %d/%d", curr_ac + 1, ac); putstr(pbuf); } if (linenums) { - (void)snprintf(pbuf, sizeof(pbuf), + (void)snprintf(pbuf, sizeof(pbuf) - 1, " line %d", currline(BOTTOM)); putstr(pbuf); } + (void)snprintf(pbuf, sizeof(pbuf) - 1, " col %d", horiz_off); + putstr(pbuf); if ((pos = position(BOTTOM)) != NULL_POSITION) { - (void)snprintf(pbuf, sizeof(pbuf), " byte %qd", pos); + (void)snprintf(pbuf, sizeof(pbuf) - 1, + " byte %qd", pos); putstr(pbuf); if (!ispipe && (len = ch_length())) { - (void)snprintf(pbuf, sizeof(pbuf), + (void)snprintf(pbuf, sizeof(pbuf) - 1, "/%qd pct %qd%%", len, ((100 * pos) / len)); putstr(pbuf); } } so_exit(); - longprompt = 0; } else { so_enter(); @@ -235,144 +387,173 @@ prompt() else if (!ispipe && (pos = position(BOTTOM)) != NULL_POSITION && (len = ch_length())) { - (void)snprintf(pbuf, sizeof(pbuf), + (void)snprintf(pbuf, sizeof(pbuf) - 1, " (%qd%%)", ((100 * pos) / len)); putstr(pbuf); } so_exit(); } - return(1); -} - -/* get command character. */ -static -getcc() -{ - extern int cmdstack; - int ch; - off_t position(); - - /* left over from error() routine. */ - if (cmdstack) { - ch = cmdstack; - cmdstack = NULL; - return(ch); - } - if (cp > cmdbuf && position(TOP) == NULL_POSITION) { - /* - * Command is incomplete, so try to complete it. - * There are only two cases: - * 1. We have "/string" but no newline. Add the \n. - * 2. We have a number but no command. Treat as #g. - * (This is all pretty hokey.) - */ - if (mca != A_DIGIT) - /* Not a number; must be search string */ - return('\n'); - else - /* A number; append a 'g' */ - return('g'); - } - return(getchr()); -} - -/* execute a multicharacter command. */ -static -exec_mca() -{ - extern int file; - extern char *tagfile; - register char *p; - char *glob(); - - *cp = '\0'; - CMD_EXEC; - switch (mca) { - case A_F_SEARCH: - (void)search(1, cmdbuf, number, wsearch); - break; - case A_B_SEARCH: - (void)search(0, cmdbuf, number, wsearch); - break; - case A_EXAMINE: - for (p = cmdbuf; isspace(*p); ++p); - (void)edit(glob(p)); - break; - case A_TAGFILE: - for (p = cmdbuf; isspace(*p); ++p); - findtag(p); - if (tagfile == NULL) - break; - if (edit(tagfile)) - (void)tagsearch(); - break; - } -} - -/* add a character to a multi-character command. */ -static -mca_char(c) - int c; -{ - switch (mca) { - case 0: /* not in a multicharacter command. */ - case A_PREFIX: /* in the prefix of a command. */ - return(NO_MCA); - case A_DIGIT: - /* - * Entering digits of a number. - * Terminated by a non-digit. - */ - if (!isascii(c) || !isdigit(c) && - c != erase_char && c != kill_char && c != werase_char) { - /* - * Not part of the number. - * Treat as a normal command character. - */ - *cp = '\0'; - number = atoi(cmdbuf); - CMD_RESET; - mca = 0; - return(NO_MCA); - } - break; - } - - /* - * Any other multicharacter command - * is terminated by a newline. - */ - if (c == '\n' || c == '\r') { - exec_mca(); - return(MCA_DONE); - } - - /* append the char to the command buffer. */ - if (cmd_char(c)) - return(MCA_DONE); - - return(MCA_MORE); + return 1; } +/* + * Sets the current prompt. Currently it sets the current prompt to the + * long prompt. + */ +statprompt(nostatprompt) + int nostatprompt; /* Turn off the stat prompt? (off by default...) */ +{ + if (nostatprompt) + longprompt = 0; + else + longprompt = 1; +} + + +/***************************************************************************** + * + * Errors, next-of-kin to prompts. + * + */ + +/* + * Shortcut function that may be used when setting the current erreur + * and erreur string at the same time. The function name is chosen to be + * symetric with the SETERR() macro in less.h. This could be written as + * macro, too, but we'd need to use a GNU C extension. + */ +SETERRSTR(enum error e, const char *s, ...) +{ + va_list args; + + erreur = e; + if (errstr) free(errstr); + errstr = NULL; + va_start(args, s); + vasprintf(&errstr, s, args); + va_end(args); +} + +/* + * Prints an error message and clears the current error. + */ +handle_error() +{ + if (erreur == E_OK) + return; + + bell(); + if (errstr) + error(errstr); + else + error(deferr[erreur]); + erreur = E_OK; + errstr = NULL; +} + +/* + * Clears any error messages and pretends they never occurred. + */ +clear_error() +{ + erreur = E_OK; + if (errstr) free(errstr); + errstr = NULL; +} + +int errmsgs; +static char return_to_continue[] = "(press RETURN)"; + +/* + * Output a message in the lower left corner of the screen + * and wait for carriage return. + */ +/* static */ +error(s) + char *s; +{ + extern int any_display; + int ch; + + errmsgs++; + if (!any_display) { + /* + * Nothing has been displayed yet. Output this message on + * error output (file descriptor 2) and don't wait for a + * keystroke to continue. + * + * This has the desirable effect of producing all error + * messages on error output if standard output is directed + * to a file. It also does the same if we never produce + * any real output; for example, if the input file(s) cannot + * be opened. If we do eventually produce output, code in + * edit() makes sure these messages can be seen before they + * are overwritten or scrolled away. + */ + (void)write(2, s, strlen(s)); + (void)write(2, "\n", 1); + return; + } + + lower_left(); + clear_eol(); + so_enter(); + if (s) { + putstr(s); + putstr(" "); + } + putstr(return_to_continue); + so_exit(); + + if ((ch = getchr()) != '\n') { + /* XXX hardcoded */ + if (ch == 'q') + quit(); + ungotcc = ch; + } + lower_left(); + + if ((s==NULL)?0:(strlen(s)) + sizeof(return_to_continue) + + so_width + se_width + 1 > sc_width) { + /* + * Printing the message has probably scrolled the screen. + * {{ Unless the terminal doesn't have auto margins, + * in which case we just hammered on the right margin. }} + */ + /* XXX Should probably just set screen_trashed=1, but I'm + * not going to touch that until all the places that call + * error() have been checked, or until error() is staticized. */ + repaint(); + } + flush(); +} + + +/**************************************************************************** + * + * The main command processor. + * + * (Well, it deals with things on the prompt line, doesn't it?) + * + */ + /* * Main command processor. + * * Accept and execute commands until a quit command, then return. */ commands() { - register int c; - register int action; - static int default_hscroll = 1; - static int saved_horiz_off = NO_HORIZ_OFF; - extern char *tagfile; - - last_mca = 0; - scroll = (sc_height + 1) / 2; + enum runmacro runmacro(); + enum runmacro rmret; + long numberN; + enum { NOTGOTTEN=0, GOTTEN=1, GETTING } Nstate; /* ie. numberNstate */ + int c; + char inbuf[20], *incur = inbuf; + *inbuf = '\0'; + Nstate = GETTING; for (;;) { - mca = 0; - number = 0; - /* * See if any signals need processing. */ @@ -380,287 +561,77 @@ commands() psignals(); /* - * Display prompt and accept a character. + * Display prompt and generally get setup. Don't display the + * prompt if we are already in the middle of accepting a + * set of characters. */ - CMD_RESET; - if (!prompt()) { + if (!*inbuf && !prompt()) { next_file(1); continue; } - noprefix(); + c = getcc(); -again: if (sigs) - continue; + /* Check sigs here --- getcc() may have given us READ_INTR */ + if (sigs) { + /* terminate any current macro */ + *inbuf = '\0'; + incur = inbuf; - /* - * If we are in a multicharacter command, call mca_char. - * Otherwise we call cmd_decode to determine the - * action to be performed. - */ - if (mca) - switch (mca_char(c)) { - case MCA_MORE: - /* - * Need another character. - */ - c = getcc(); - goto again; - case MCA_DONE: - /* - * Command has been handled by mca_char. - * Start clean with a prompt. - */ - continue; - case NO_MCA: - /* - * Not a multi-char command - * (at least, not anymore). - */ - break; - } - - /* decode the command character and decide what to do. */ - switch (action = cmd_decode(c)) { - case A_DIGIT: /* first digit of a number */ - start_mca(A_DIGIT, ":"); - goto again; - case A_F_SCREEN: /* forward one screen */ - CMD_EXEC; - if (number <= 0 && (number = sc_window) <= 0) - number = sc_height - 1; - forward(number, 1); - break; - case A_B_SCREEN: /* backward one screen */ - CMD_EXEC; - if (number <= 0 && (number = sc_window) <= 0) - number = sc_height - 1; - backward(number, 1); - break; - case A_F_LINE: /* forward N (default 1) line */ - CMD_EXEC; - forward(number <= 0 ? 1 : number, 0); - break; - case A_B_LINE: /* backward N (default 1) line */ - CMD_EXEC; - backward(number <= 0 ? 1 : number, 0); - break; - case A_R_COL: /* to the right N (default 1) cols */ - /* XXX Should beep here rather than silently truncating - * lines in line.c when we are about to exceed the - * line buffer. */ - if (number > 0) - default_hscroll = number; - horiz_off += default_hscroll; - repaint(); - break; - case A_L_COL: /* to the left N (default 1) cols */ - if (number > 0) - default_hscroll = number; - if (horiz_off != 0 && horiz_off != NO_HORIZ_OFF) { - horiz_off -= default_hscroll; - if (horiz_off < 0) - horiz_off = 0; - } else - horiz_off = NO_HORIZ_OFF; - repaint(); - break; - case A_HOME: - if (horiz_off != NO_HORIZ_OFF) { - saved_horiz_off = horiz_off; - horiz_off = NO_HORIZ_OFF; - } else - horiz_off = saved_horiz_off; - repaint(); - break; - case A_F_SCROLL: /* forward N lines */ - CMD_EXEC; - if (number > 0) - scroll = number; - forward(scroll, 0); - break; - case A_B_SCROLL: /* backward N lines */ - CMD_EXEC; - if (number > 0) - scroll = number; - backward(scroll, 0); - break; - case A_FREPAINT: /* flush buffers and repaint */ - if (!ispipe) { - ch_init(0, 0); - clr_linenum(); - } - /* FALLTHROUGH */ - case A_REPAINT: /* repaint the screen */ - CMD_EXEC; - repaint(); - break; - case A_GOLINE: /* go to line N, default 1 */ - CMD_EXEC; - if (number <= 0) - number = 1; - jump_back(number); - break; - case A_PERCENT: /* go to percent of file */ - CMD_EXEC; - if (number < 0) - number = 0; - else if (number > 100) - number = 100; - jump_percent(number); - break; - case A_GOEND: /* go to line N, default end */ - CMD_EXEC; - if (number <= 0) - jump_forw(); - else - jump_back(number); - break; - case A_STAT: /* print file name, etc. */ - longprompt = 1; - continue; - case A_QUIT: /* exit */ - quit(); - case A_F_SEARCH: /* search for a pattern */ - case A_B_SEARCH: - if (number <= 0) - number = 1; - start_mca(action, (action==A_F_SEARCH) ? "/" : "?"); - last_mca = mca; - wsearch = 1; - c = getcc(); - if (c == '!') { - /* - * Invert the sense of the search; set wsearch - * to 0 and get a new character for the start - * of the pattern. - */ - start_mca(action, - (action == A_F_SEARCH) ? "!/" : "!?"); - wsearch = 0; - c = getcc(); - } - goto again; - case A_AGAIN_SEARCH: /* repeat previous search */ - if (number <= 0) - number = 1; - if (wsearch) - start_mca(last_mca, - (last_mca == A_F_SEARCH) ? "/" : "?"); - else - start_mca(last_mca, - (last_mca == A_F_SEARCH) ? "!/" : "!?"); - CMD_EXEC; - (void)search(mca == A_F_SEARCH, (char *)NULL, - number, wsearch); - break; - case A_HELP: /* help */ - if (ac > 0 && !strcmp(_PATH_HELPFILE, av[curr_ac])) { - error("Already viewing help."); - break; - } - lower_left(); - clear_eol(); - putstr("help"); - CMD_EXEC; - help(); - break; - case A_TAGFILE: /* tag a new file */ - CMD_RESET; - start_mca(A_TAGFILE, "Tag: "); - c = getcc(); - goto again; - case A_NEXTTAG: - if (number <= 0) - number = 1; - nexttag(number); - if (tagfile == NULL) - break; - if (edit(tagfile)) - (void)tagsearch(); - break; - case A_PREVTAG: - if (number <= 0) - number = 1; - prevtag(number); - if (tagfile == NULL) - break; - if (edit(tagfile)) - (void)tagsearch(); - break; - case A_FILE_LIST: /* show list of file names */ - CMD_EXEC; - showlist(); - repaint(); - break; - case A_EXAMINE: /* edit a new file */ - CMD_RESET; - start_mca(A_EXAMINE, "Examine: "); - c = getcc(); - goto again; - case A_VISUAL: /* invoke the editor */ - if (ispipe) { - error("Cannot edit standard input"); - break; - } - CMD_EXEC; - editfile(); - ch_init(0, 0); - clr_linenum(); - break; - case A_NEXT_FILE: /* examine next file */ - if (number <= 0) - number = 1; - next_file(number); - break; - case A_PREV_FILE: /* examine previous file */ - if (number <= 0) - number = 1; - prev_file(number); - break; - case A_SETMARK: /* set a mark */ - lower_left(); - clear_eol(); - start_mca(A_SETMARK, "mark: "); - c = getcc(); - if (c == erase_char || c == kill_char) - break; - setmark(c); - break; - case A_GOMARK: /* go to mark */ - lower_left(); - clear_eol(); - start_mca(A_GOMARK, "goto mark: "); - c = getcc(); - if (c == erase_char || c == kill_char) - break; - gomark(c); - break; - case A_PREFIX: - /* - * The command is incomplete (more chars are needed). - * Display the current char so the user knows what's - * going on and get another character. - */ - if (mca != A_PREFIX) - start_mca(A_PREFIX, ""); - if (CONTROL_CHAR(c)) { - putchr('^'); - c &= ~0200; - c = CARAT_CHAR(c); - } - putchr(c); - c = getcc(); - goto again; - default: - bell(); - break; + continue; /* process the sigs */ } - } + + if (Nstate == GETTING && !isdigit(c)) { + /* mark the end of an input number N, if any */ + + if (!*inbuf) { + /* We never actually got an input number */ + Nstate = NOTGOTTEN; + } else { + numberN = atol(inbuf); + Nstate = GOTTEN; + } + *inbuf = '\0'; + incur = inbuf; + } + cmd_char(c, inbuf, &incur, inbuf + sizeof(inbuf) - 1); + *incur = '\0'; + if (*inbuf) prmpt(inbuf); + + if (Nstate == GETTING) { + /* Still reading in the number N ... don't want to + * try running the macro expander. */ + continue; + } else { + /* Try expanding the macro */ + switch (runmacro(inbuf, numberN, Nstate)) { + case TOOMACRO: + break; + case BADMACRO: case NOMACRO: case BADCOMMAND: + handle_error(); + /* fallthrough */ + case OK: + /* recock */ + *inbuf = '\0'; + incur = inbuf; + Nstate = GETTING; + break; + } + } + } /* for (;;) */ } + +/***************************************************************************** + * + * Misc functions that belong in ncommand.c but are here for historical + * and for copyright reasons. + * + */ + editfile() { + off_t position(); extern char *current_file; static int dolinenumber; static char *editor; diff --git a/usr.bin/more/decode.c b/usr.bin/more/decode.c deleted file mode 100644 index 4a3315275946..000000000000 --- a/usr.bin/more/decode.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (c) 1988 Mark Nudleman - * Copyright (c) 1988, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef lint -static char sccsid[] = "@(#)decode.c 8.1 (Berkeley) 6/6/93"; -#endif /* not lint */ - -#ifndef lint -static const char rcsid[] = - "$FreeBSD$"; -#endif /* not lint */ - -/* - * Routines to decode user commands. - * - * This is all table driven. - * A command table is a sequence of command descriptors. - * Each command descriptor is a sequence of bytes with the following format: - * ...<0> - * The characters c1,c2,...,cN are the command string; that is, - * the characters which the user must type. - * It is terminated by a null <0> byte. - * The byte after the null byte is the action code associated - * with the command string. - * - * The default commands are described by cmdtable. - */ - -#include -#include - -#include - -#include "less.h" - -/* - * Command table is ordered roughly according to expected - * frequency of use, so the common commands are near the beginning. - */ -#define CONTROL(c) ((c)&037) - -/* - * Ideally the home and end keys would reset the horiz_scroll, too, - * but this whole thing needs to be made dynamic along with some type - * of macro commands. - */ -static char cmdtable[] = { - '\e','[','B',0, A_F_LINE, - '\e','[','A',0, A_B_LINE, - '\e','[','C',0, A_R_COL, - '\e','[','D',0, A_L_COL, - '\r',0, A_F_LINE, - '\n',0, A_F_LINE, - 'j',0, A_F_LINE, - 'k',0, A_B_LINE, - 'd',0, A_F_SCROLL, - CONTROL('D'),0, A_F_SCROLL, - 'u',0, A_B_SCROLL, - CONTROL('U'),0, A_B_SCROLL, - ' ',0, A_F_SCREEN, - 'f',0, A_F_SCREEN, - CONTROL('F'),0, A_F_SCREEN, - '\e','[','G',0, A_F_SCREEN, - 'b',0, A_B_SCREEN, - CONTROL('B'),0, A_B_SCREEN, - '\e','[','I',0, A_B_SCREEN, - 'R',0, A_FREPAINT, - 'r',0, A_REPAINT, - CONTROL('L'),0, A_REPAINT, - 'g',0, A_GOLINE, - '\e','[','H',0, A_HOME, - 'p',0, A_PERCENT, - '%',0, A_PERCENT, - 'G',0, A_GOEND, - '\e','[','F',0, A_GOEND, - '0',0, A_DIGIT, - '1',0, A_DIGIT, - '2',0, A_DIGIT, - '3',0, A_DIGIT, - '4',0, A_DIGIT, - '5',0, A_DIGIT, - '6',0, A_DIGIT, - '7',0, A_DIGIT, - '8',0, A_DIGIT, - '9',0, A_DIGIT, - - '=',0, A_STAT, - CONTROL('G'),0, A_STAT, - '/',0, A_F_SEARCH, - '?',0, A_B_SEARCH, - 'n',0, A_AGAIN_SEARCH, - 'm',0, A_SETMARK, - '\'',0, A_GOMARK, - 'E',0, A_EXAMINE, - 'N',0, A_NEXT_FILE, - ':','n',0, A_NEXT_FILE, - 'P',0, A_PREV_FILE, - ':','p',0, A_PREV_FILE, - 'v',0, A_VISUAL, - - 'h',0, A_HELP, - 'q',0, A_QUIT, - ':','q',0, A_QUIT, - ':','t',0, A_TAGFILE, - 'T',0, A_PREVTAG, - 't',0, A_NEXTTAG, - ':', 'a', 0, A_FILE_LIST, - 'Z','Z',0, A_QUIT, -}; - -char *cmdendtable = cmdtable + sizeof(cmdtable); - -#define MAX_CMDLEN 16 - -static char kbuf[MAX_CMDLEN+1]; -static char *kp = kbuf; - -/* - * Indicate that we're not in a prefix command - * by resetting the command buffer pointer. - */ -noprefix() -{ - kp = kbuf; -} - -/* - * Decode a command character and return the associated action. - */ -cmd_decode(c) - int c; -{ - register int action = A_INVALID; - - /* - * Append the new command character to the command string in kbuf. - */ - *kp++ = c; - *kp = '\0'; - - action = cmd_search(cmdtable, cmdendtable); - - /* This is not a prefix character. */ - if (action != A_PREFIX) - noprefix(); - return(action); -} - -/* - * Search a command table for the current command string (in kbuf). - */ -cmd_search(table, endtable) - char *table; - char *endtable; -{ - register char *p, *q; - - for (p = table, q = kbuf; p < endtable; p++, q++) { - if (*p == *q) { - /* - * Current characters match. - * If we're at the end of the string, we've found it. - * Return the action code, which is the character - * after the null at the end of the string - * in the command table. - */ - if (*p == '\0') - return(p[1]); - } - else if (*q == '\0') { - /* - * Hit the end of the user's command, - * but not the end of the string in the command table. - * The user's command is incomplete. - */ - return(A_PREFIX); - } else { - /* - * Not a match. - * Skip ahead to the next command in the - * command table, and reset the pointer - * to the user's command. - */ - while (*p++ != '\0'); - q = kbuf-1; - } - } - /* - * No match found in the entire command table. - */ - return(A_INVALID); -} diff --git a/usr.bin/more/default.morerc b/usr.bin/more/default.morerc new file mode 100644 index 000000000000..7a2ccb433422 --- /dev/null +++ b/usr.bin/more/default.morerc @@ -0,0 +1,134 @@ +# +# This is the default initialization file for more(1). To avoid any need to +# change the manpage or helpfile, almost all commands maintain their historical +# keymappings. Some additional twoggles may be added that will be left for +# the intrepid user to discover. +# +# This file is compiled directly into more; changing this file will not change +# the actual defaults (unless it is changed in the source directory and more +# is recompiled). The correct way to change the global defaults is by +# adding a /etc/dot.morerc global initialization file. +# +# If you use an ~/.morerc that is dependent on specific features of this +# default morerc, you should copy this default morerc to ~/.defmorerc so that +# possible future changes in this file do not cause problems for you. The +# ~/.defmorerc file will cause the compiled-in default morerc to be ignored. +# +# The default initialization file is compiled into more(1) so that more(1) +# will work and be usable even if the filesystem (and the location +# /usr/share/misc/default.morerc, where this would be stored if it was not +# compiled into more(1)) is missing or away without leave (chroot directory, +# fs crash, badly written rescue floppy, or any other reason). +# +# BUGS: a) There is no documentation. +# b) There is no "map" command. +# +# $FreeBSD$ +# + +# required -- initialize more(1) +deftog + +# +# basic internal initialization +# +set lsthscr 1 +# Add "set hkey_scroll true" to ~/.morerc to enable all the hjkl keys (but +# disabling h)elp). +set hkey_scroll false +set scr_scroll 0 +# We have no way of resetting this on SIGWINCH as the old more A_H_SCROLL did +# It's probably just as well... (since resetting would lose the old value!) +# (Actually, we could emulate it from here if we really wanted to). +set half_scroll (${_sc_height} / 2) +# magic number indicating the value is not initialized +set savedhscroll 87382 + +macro 1 j 'forw_scroll ${number}' +macro 1 k 'back_scroll ${number}' +macro 1 \e[B 'forw_scroll ${number}' +macro 1 \e[A 'back_scroll ${number}' +macro 1 \n 'forw_scroll ${number}' +macro 1 \e[G 'forw (${_sc_height} * ${number})' +macro 1 \e[I 'back (${_sc_height} * ${number})' +set com_getscr 'condition (${number} != 0); \ + set scr_scroll ${number}; \ + condition (${number} == 0); \ + set scr_scroll ${_sc_height}; \ + condition true;' +macro 0 " " 'eval ${com_getscr}; forw ${scr_scroll};' +macro 0 f 'eval ${com_getscr}; forw ${scr_scroll};' +macro 0 "" 'eval ${com_getscr}; forw ${scr_scroll};' +macro 0 b 'eval ${com_getscr}; back ${scr_scroll};' +macro 0 "" 'eval ${com_getscr}; back ${scr_scroll};' +set com_sethalfscroll 'condition (${number} != 0); \ + set half_scroll ${number}; \ + condition true;' +macro 0 d 'eval ${com_sethalfscroll}; forw_scroll ${half_scroll}' +macro 0 "" 'eval ${com_sethalfscroll}; forw_scroll ${half_scroll}' +macro 0 u 'eval ${com_sethalfscroll}; back_scroll ${half_scroll}' +macro 0 "" 'eval ${com_sethalfscroll}; back_scroll ${half_scroll}' +set com_rscroll 'condition (${number} != 0); \ + set lsthscr ${number}; \ + condition true; \ + rscroll ${lsthscr};' +set com_lscroll 'condition (${number} != 0); \ + set lsthscr ${number}; \ + condition true; \ + lscroll ${lsthscr};' +# this little trick lets the user simply set hkey_scroll=true in their own +# ~/.morerc file to enable the 'l' and 'h' keys the way Bill meant them +macro 0 h 'condition ${hkey_scroll}; eval ${com_lscroll}; \ + condition_! ${hkey_scroll}; help; \ + condition true;' +macro 0 l 'condition ${hkey_scroll}; eval ${com_rscroll}; \ + condition_! ${hkey_scroll}; error "key not enabled"; \ + condition true;' +macro 0 :help 'help' +macro 0 \e[C 'eval ${com_rscroll}' +macro 0 \e[D 'eval ${com_lscroll}' +macro 0 \e[H 'condition (${_wraplines_n} && (${savedhscroll} != 87382)); \ + rscroll 1; \ + rscroll ${savedhscroll}; \ + condition_toggle; \ + set savedhscroll ${_curhscroll}; \ + lscroll ${_curhscroll}; \ + lscroll 1; \ + condition true;' +macro 1 n 'research ${_ls_direction_n} ${number}' +macro 1 N 'research (${_ls_direction_n} + 1) ${number}' +macro 1 / 'magicasksearch forw ${number}' +macro 1 ? 'magicasksearch back ${number}' +macro 0 G 'condition (${number} == 0); goend; \ + condition (${number} != 0); goline ${number}; \ + condition true;' +macro 1 g 'goline ${number}' +macro 0 p 'gopercent ${number}' +macro 0 % 'gopercent ${number}' +macro 0 \e[F 'goend' +# Quote since it's technically an isspace() character +macro 0 " " 'repaint' +macro 0 r 'repaint' +macro 0 R 'flush' +macro 0 v 'edit' +macro 0 :e 'askfile' +macro 0 E 'askfile' +# The old keymaping for 'N' +#macro 1 N 'file next ${number}' +macro 1 :n 'file next ${number}' +macro 1 P 'file prev ${number}' +macro 1 :p 'file prev ${number}' +macro 0 :a 'file_list' +macro 0 m 'setmark ?' +macro 0 \' 'gomark ?' +macro 0 :t 'asktag' +macro 1 t 'nexttag ${number}' +macro 1 T 'prevtag ${number}' +macro 0 "" 'stat (${_stat_n} + 1)' +macro 0 = 'stat (${_stat_n} + 1)' +macro 0 q 'quit' +macro 0 :q 'quit' +macro 0 ZZ 'quit' +# This command intentionally disabled by default. The command parser is +# too baroque to expose hapless users to. +#macro 0 :: 'usercom' diff --git a/usr.bin/more/input.c b/usr.bin/more/input.c index 50e2031724ab..1ecd34633efd 100644 --- a/usr.bin/more/input.c +++ b/usr.bin/more/input.c @@ -55,7 +55,9 @@ static const char rcsid[] = #include "less.h" -int horiz_off = NO_HORIZ_OFF; /* # characters scrolled off left of screen */ +/* NOTE!: if (wraplines) assert (horiz_off == 0) */ +int horiz_off = 0; /* # characters scrolled off left of screen */ +int wraplines = 1; /* wrap lines around screen, yes or no */ extern int squeeze; extern int sigs; @@ -103,7 +105,7 @@ forw_line(curr_pos) /* * Append the char to the line and get the next char. * The pappend() will throw away any unimportant chars - * (ie. not underlines or bolds) as per horiz_off. + * (ie. not underlines or bolds) as per wraplines. * * XXX line.c needs to be rewritten... */ @@ -114,7 +116,7 @@ forw_line(curr_pos) * is too long to print in the screen width. * End the line here. */ - if (horiz_off != NO_HORIZ_OFF) { + if (!wraplines) { /* Throw away left-over characters on line */ c = ch_forw_get(); while (c != '\n' && c != EOI) @@ -247,7 +249,7 @@ back_line(curr_pos) break; if (pappend(c)) { - if (horiz_off == NO_HORIZ_OFF) { + if (wraplines) { /* * Got a full printable line, but we haven't * reached our curr_pos yet. Discard the line diff --git a/usr.bin/more/less.h b/usr.bin/more/less.h index e17ec4038210..f30abd9b5fc3 100644 --- a/usr.bin/more/less.h +++ b/usr.bin/more/less.h @@ -36,6 +36,8 @@ * $FreeBSD$ */ +#define MAXVARLENGTH (20) + #define NULL_POSITION ((off_t)(-1)) #define EOI (0) @@ -58,37 +60,77 @@ #define BOTTOM_PLUS_ONE (-2) #define MIDDLE (-3) -#define A_INVALID -1 +/* The return type of runmacro() */ +enum runmacro { OK=0, TOOMACRO, BADMACRO, NOMACRO, BADCOMMAND }; -#define A_AGAIN_SEARCH 1 -#define A_B_LINE 2 -#define A_B_SCREEN 3 -#define A_B_SCROLL 4 -#define A_B_SEARCH 5 -#define A_DIGIT 6 -#define A_EXAMINE 7 -#define A_FREPAINT 8 -#define A_F_LINE 9 -#define A_F_SCREEN 10 -#define A_F_SCROLL 11 -#define A_F_SEARCH 12 -#define A_GOEND 13 -#define A_GOLINE 14 -#define A_GOMARK 15 -#define A_HELP 16 -#define A_NEXT_FILE 17 -#define A_PERCENT 18 -#define A_PREFIX 19 -#define A_PREV_FILE 20 -#define A_QUIT 21 -#define A_REPAINT 22 -#define A_SETMARK 23 -#define A_STAT 24 -#define A_VISUAL 25 -#define A_TAGFILE 26 -#define A_FILE_LIST 27 -#define A_L_COL 28 -#define A_R_COL 29 -#define A_HOME 30 -#define A_NEXTTAG 31 -#define A_PREVTAG 32 +#ifdef DEFINEGLOBALS +#define GLOBAL(var, val) var = val +#else +#define GLOBAL(var, val) extern var +#endif + + +/* + * This style of error-reporting (see also command.c) is only used by some + * code. Eventually most of the code should use it, since it is becoming + * inconvenient to have John Q. random function() calling error(). + * + * This style of error-reporting still leaves somewhat to be desired.... + * + * Note that more(1) needs to potentially work under low-memory conditions + * (such as may occur when all available memory has been sucked-up by + * the file buffer in ch.c). + */ + +/* Be careful about ordering correctly!! (must match deferrinit_) */ +enum error { E_OK=0, E_AMBIG, E_BADMATH, E_BADVAR, E_BOGCOM, E_CANTPARSE, + E_CANTXPND, E_COMPLIM, E_EXTERN, E_NOMAC, E_MALLOC, E_NONUM, + E_NOSTR, E_NOTOG, E_NULL }; + +/* Listed here for reference only. Be careful about ordering correctly!! */ +#define deferrinit_ { \ + "", /* E_OK */ \ + "ambigious macro", /* E_AMBIG */ \ + "invalid arithmetic expression", /* E_BADMATH */ \ + "bad variable", /* E_BADVAR */ \ + "bogus command", /* E_BOGCOM */ \ + "could not parse command string", /* E_CANTPARSE */ \ + "could not expand macro", /* E_CANTXPND */ \ + "compile time limit", /* E_COMPLIM */ \ + "external dependency error", /* E_EXTERN */ \ + "could not find match for macro", /* E_NOMAC */ \ + "malloc() failed", /* E_MALLOC */ \ + "missing numeric argument to command", /* E_NONUM */ \ + "missing string argument to command", /* E_NOSTR */ \ + "bad n-toggle argument to command", /* E_NOTOG */ \ + "to the unknown error", /* E_NULL */ \ +} +GLOBAL(const char *deferr[], deferrinit_ ); + +/* + * It is possible for erreur to become dis-synchronized from errstr if + * its users aren't careful. Access through the macros is considered + * careful. + */ +GLOBAL(enum error erreur, NULL); +GLOBAL(char *errstr, NULL); /* must point be null or free()'ble */ + +#define SETERR(e) do { \ + erreur = (e); \ + if (errstr) free(errstr); \ + errstr = NULL; \ + } while (0) +/* SETERRSTR() also exists. It is in command.c */ + +/* + * An emalloc() traditionally never fails, but fmalloc() may fail, hence + * the non-standard name. The fmalloc() is just syntactic sugar that sets + * erreur for the user. + * + * fmalloc(size, pointer-to-new-memory); + * + * Don't compile this puppy with -Wall... + */ + +#define FMALLOC(s,v) ((((v) = malloc(s)) ? 0 : \ + ((errstr ? free(errstr), errstr=NULL : 0), erreur = E_MALLOC)), (v)) diff --git a/usr.bin/more/less.morerc b/usr.bin/more/less.morerc new file mode 100644 index 000000000000..95fb9054cf05 --- /dev/null +++ b/usr.bin/more/less.morerc @@ -0,0 +1,61 @@ +# +# This sample .morerc causes more to emulate the default GNU less(1) +# keys, in so far as more(1) is capable (which is not very far at the +# moment). +# +# Some of this will/should/may be eventually merged into default.morerc. +# +# $FreeBSD$ +# + +# magic value indicating we should use ${sc_height} +set window 2424989898 + +macro 0 H 'help' +set com_getscr 'condition (${number} == 0); \ + condition (${window} == 2424989898); \ + set scr_scroll ${sc_height}; \ + condition_toggle; \ + set scr_scroll ${window}; \ + condition (${number} != 0); \ + set scr_scroll ${number}; \ + condition true;' +macro 0 "" 'eval ${com_getscr}; forw ${scr_scroll};' +macro 0 z 'condition (${number} == 0); \ + eval ${com_getscr}; forw ${scr_scroll}; \ + condition (${number} != 0); \ + set window ${number}; forw ${number}; \ + condition true;' +macro 0 w 'condition (${number} == 0); \ + eval ${com_getscr}; back ${scr_scroll}; \ + condition (${number} != 0); \ + set window ${number}; back ${number}; \ + condition true;' +macro 0 "\ev" 'eval ${com_getscr}; back ${scr_scroll};' +macro 1 "" 'forw_scroll ${number};' +macro 1 e 'forw_scroll ${number};' +macro 1 "" 'forw_scroll ${number};' +macro 1 y 'back_scroll ${number};' +macro 1 "" 'back_scroll ${number};' +macro 1 "" 'back_scroll ${number};' +macro 1 " " 'back_scroll ${number};' +macro 8 \e) 'rscroll ${number};' +macro 8 \e[C 'rscroll ${number};' +macro 8 \e( 'lscroll ${number};' +macro 8 \e[D 'lscroll ${number};' +macro 1 "<" 'goline ${number};' +macro 1 "\e<" 'goline ${number}; +macro 0 ">" 'condition (${number} == 0); goend; \ + condition (${number} != 0); goline ${number}; \ + condition true;' +macro 0 "\e>" 'condition (${number} == 0); goend; \ + condition (${number} != 0); goline ${number}; \ + condition true;' +macro 0 "" 'gomark ?;' +macro 0 "" 'askfile;' +macro 0 :f 'stat (${_stat_n} + 1);' +macro 0 V 'error "Less is not being run!" +macro 0 Q 'quit;' +macro 0 :Q 'quit;' +# It might be possible to match the brace/bracket-pairing feature of +# less using some complex regexps... Project for a rain weekend... :-) diff --git a/usr.bin/more/line.c b/usr.bin/more/line.c index 6b980739b1dc..f974e8303343 100644 --- a/usr.bin/more/line.c +++ b/usr.bin/more/line.c @@ -111,7 +111,7 @@ prewind() { line = curr = linebuf; ln_state = LN_NORMAL; - column = (horiz_off == NO_HORIZ_OFF) ? 0 : -horiz_off; + column = -horiz_off; } /* @@ -379,14 +379,9 @@ pappend(c) /* * Expand a tab into spaces. */ - if (horiz_off != NO_HORIZ_OFF) - do { - NEW_COLUMN(1); - } while (((column + horiz_off) % tabstop) != 0); - else - do { - NEW_COLUMN(1); - } while ((column % tabstop) != 0); + do { + NEW_COLUMN(1); + } while (((column + horiz_off) % tabstop) != 0); *curr++ = '\t'; return (0); } diff --git a/usr.bin/more/macro.c b/usr.bin/more/macro.c new file mode 100644 index 000000000000..1fdd56ec919d --- /dev/null +++ b/usr.bin/more/macro.c @@ -0,0 +1,292 @@ +/*- + * Copyright (c) 1999 Timmy M. Vanderhoek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef lint +static const char rcsid[] = + "$Id$"; +#endif /* not lint */ + +/* + * Expansion of macros. + */ + +#include +#include +#include + +#include "less.h" + + +/* + * Used to construct tables of macros. Each macro string expands to command. + * A number N is associated with each execution of a macro. The command + * "set number " will be done before the expansion. The end of the table is + * specified by mactabsize. A NULL entry for command denotes a macro that + * has been marked deleted for some reason. As of this writing, there is no + * code that actually deletes a macro... + */ +struct macro { + char *string; /* characters typed to activate macro */ + char *command; /* command resulting after the macro is activated */ + long defnumber; /* default value of the N number */ + int flags; /* only holds STICKYNUMB for now... */ +}; +/* (struct macro) ->flags */ +#define NOFLAGS 0 +#define STICKYNUMB 1 /* Set defnumber to current number, if current number */ + +/* + * The macro table. + */ +struct macro *mactab = NULL; +int mactabsize = 0; + +static enum runmacro runmacro_(); +static struct macro *matchmac(); + + +/* + * XXX Everything's really just a macro until resolved as a quantum wave + * probability distribution. + */ + + +/* + * Attempts to run the appropriate macro. Returns 0, or OK, if the macro + * was succesfully run. Returns BADMACRO and sets erreur if something is + * horribly wrong with the macro. Returns NOMACRO if the macro has no valid + * expansion. BADMACRO and NOMACRO are almost the same. Returns BADCOMMAND + * and leaves erreur set (hopefully it was set when runmacro() tried to execute + * the command associated with the macro) if the command associated with + * the macro was unsuccessful. Returns TOOMACRO if the macro appears to be + * incomplete (ie. the user has not finished typing it in yet). The erreur + * is not set in this case. + * + * XXX There's no good reason not to just disallow badmacros from within + * setmacro() ... It's not clear what the author was thinking at the time. + */ +enum runmacro +runmacro(macro, number, anyN) + const char *macro; /* the macro string to try and expand */ + long number; /* the number N associated with this execution */ + int anyN; /* FALSE is we should use the default N associated with + * the macro. TRUE if we should use the number argument. */ +{ + struct macro *cur, *matched; + int match, yetmatch; + int s; + + if (!mactab) { + /* Should only happen with really sucky default rc files... */ + SETERR (E_CANTXPND); + return NOMACRO; + } + + match = yetmatch = 0; + for (cur = mactab, s = mactabsize; s; cur++, s--) { + if (!cur->command) + continue; /* deleted macro */ + if (!strcmp(cur->string, macro)) + matched = cur, match++; + else if (!strncmp(cur->string, macro, strlen(macro))) + yetmatch++; + } + + if (match == 1) { + if (yetmatch) { + SETERR (E_AMBIG); + return BADMACRO; + } + + /* XXX it's not clear how to handle error when setting + * the number N --- this is a deficiency in the style of error- + * reporting suggested in command.c and less.h. Could have + * setvar() guarantee success when setting "number". A failure + * must not become fatal or it becomes impossible to do + * any commands at all. */ + if (anyN) { + if (matched->flags & STICKYNUMB) + matched->defnumber = number; + (void) setvari("number", number); + } else + (void) setvari("number", matched->defnumber); + clear_error(); + + if (command(matched->command)) + return BADCOMMAND; + return OK; + } + if (match > 1) { + SETERR (E_AMBIG); + return BADMACRO; + } + if (!match && !yetmatch) { + SETERR (E_CANTXPND); + return NOMACRO; + } + assert(yetmatch); + return TOOMACRO; +} + +/* + * Associates a macro with a given command. Returns -1 if it was unable to + * set the macro. Errors associated with setting a macro may be caught + * either in this function, setmacro(), or in runmacro(). Both macro and + * command are strcpy()'d into their own space. + */ +int +setmacro(macro, command) + const char *macro; + const char *command; +{ + struct macro *cur, *new = NULL; + char *new_mac, *new_com; + int s; + + assert (macro); assert (command); + + /* First, check for any existing macro matches in the custom table */ + s = mactabsize; + for (cur = mactab; s; cur++, s--) { + if (!cur->command) { + /* Hmm... A deleted macro in the table */ + new = cur; + continue; + } + if (!strcmp(cur->string, macro)) { + /* + * An exact match to the new macro already exists. + * Calling realloc() on cur->string and cur->command + * without risking being left in bad state is tricky. + * Just do it the slow but sure way... + */ + new = cur; + break; + } + } + + /* + * Do the allocations here so that we can maintain consistent state + * even if realloc() fails when we try to expand the table (suppose + * the table gets expanded but the next malloc to get space for the + * macro fails). + */ + if (!FMALLOC(strlen(macro) + 1, new_mac)) + return -1; + if (!FMALLOC(strlen(command) + 1, new_com)) + return -1; + + if (!new) { + /* Extend the command table by one record */ + struct macro *t = realloc(mactab, (mactabsize + 1) * + sizeof(struct macro)); + if (!t) { + /* The old mactab is still valid. Just back out. */ + free(new_mac), free(new_com); + SETERR (E_MALLOC); + return -1; + } else + mactab = t; + new = &mactab[mactabsize]; + mactabsize++; + new->string = new->command = NULL; + } + + if (new->string) free(new->string); + if (new->command) free(new->command); + new->string = new_mac; + new->command = new_com; + strcpy(new->string, macro); + strcpy(new->command, command); + + return 0; +} + +/* + * Set the sticky tag on a macro. Returns -1 on failure, 0 on success. + */ +int +stickymac(macro, state) + const char *macro; + int state; /* set it to TRUE or set it to FALSE */ +{ + struct macro *m = matchmac(macro); + if (!m) + return -1; + + if (state) + m->flags |= STICKYNUMB; + else + m->flags &= ~STICKYNUMB; + + return 0; +} + +/* + * Set the default number of a macro. Returns -1 on failure, 0 on success. + */ +int +setmacnumb(macro, N) + const char *macro; + long N; /* The default number */ +{ + struct macro *m = matchmac(macro); + if (!m) + return -1; + + m->defnumber = N; + return 0; +} + +/* + * Tries to find a struct macro matching "macro". Returns NULL if an exact + * match could not be found (eg. ambiguous macro, no macro, etc). + */ +static struct macro * +matchmac(macro) + const char *macro; +{ + struct macro *retr, *cur; + int s; + + retr = NULL; + for (cur = mactab, s = mactabsize; s; cur++, s--) { + if (!cur->command) + continue; + if (!strcmp(cur->string, macro)) { + if (retr) { + SETERR (E_AMBIG); + return NULL; /* matched twice! */ + } else + retr = cur; + } else if (!strncmp(cur->string, macro, strlen(macro))) { + SETERR (E_AMBIG); + return NULL; /* ambiguous macro! */ + } + } + return retr; +} diff --git a/usr.bin/more/main.c b/usr.bin/more/main.c index f7159eaafbb8..cbe853c33cd8 100644 --- a/usr.bin/more/main.c +++ b/usr.bin/more/main.c @@ -56,18 +56,21 @@ static const char rcsid[] = #include #include +#include #include #include #include #include #include +#define DEFINEGLOBALS +#include "defrc.h" #include "less.h" +#include "pathnames.h" int ispipe; char *current_file, *previous_file, *current_name, *next_name; int any_display; -int scroll; int ac; char **av; int curr_ac; @@ -88,7 +91,6 @@ extern int tagoption; edit(filename) register char *filename; { - extern int errno; register int f; register char *m; off_t initial_pos, prev_pos, position(); @@ -334,8 +336,15 @@ main(argc, argv) } while (file < 0 && ++curr_ac < ac); } - if (file >= 0) + if (file >= 0) { + /* + * Don't call rcfiles() until now so that people who put + * wierd things (like "forw_scroll") in their rc file don't + * cause us to SEGV. + */ + rcfiles(); commands(); + } quit(); /*NOTREACHED*/ } @@ -375,3 +384,156 @@ quit() raw_mode(0); exit(0); } + +/* + * Read in from each of the three rc files - default, system, user. + * Calls handle_error() directly to report errors. + */ +rcfiles() +{ + FILE *fd; + char fbuf[MAXPATHLEN + 1]; + char *c; + int readrc(); + int savederrno; + static int str_read(); + + /* The default builtin rc file */ + if ((c = getenv("HOME")) && + strlen(c) + strlen(_PATH_DEFRC) + 1 < MAXPATHLEN) { + sprintf(fbuf, "%s/%s", c, _PATH_DEFRC); + fd = fopen(fbuf, "r"); + savederrno = errno; + if (!fd) { + if (!access(_PATH_SYSMORERC, F_OK)) { + SETERRSTR(E_EXTERN, "unable to read %s: %s", + _PATH_SYSMORERC, strerror(savederrno)); + handle_error(); + } + /* We'd better have some type of default keys!! */ + goto use_builtin_defrc; + } else { + readrc(fd); + fclose(fd); + } + } else { +use_builtin_defrc: + fd = fropen(DEFRC, str_read); + readrc(fd); + fclose(fd); + } + + /* The system rc file */ + fd = fopen(_PATH_SYSMORERC, "r"); + savederrno = errno; + if (!fd) { + if (!access(_PATH_SYSMORERC, F_OK)) { + SETERRSTR(E_EXTERN, "unable to read %s: %s", + _PATH_SYSMORERC, strerror(savederrno)); + handle_error(); + } else + ; /* non-existant => non-error */ + } else { + readrc(fd); + fclose(fd); + } + + /* The user rc file */ + if ((c = getenv("HOME")) && + strlen(c) + strlen(_PATH_RC) + 1 < MAXPATHLEN) { + sprintf(fbuf, "%s/%s", c, _PATH_RC); + fd = fopen(fbuf, "r"); + savederrno = errno; + if (!fd) { + if (!access(fbuf, F_OK)) { + SETERRSTR(E_EXTERN, + "unable to read %s: %s", fbuf, + strerror(savederrno)); + handle_error(); + } else + ; /* non-existant => non-error */ + } else { + readrc(fd); + fclose(fd); + } + } +} + +/* + * Read-in an rc file from a fd. Calls handle_error() directly to handle + * errors. + * + * This really belongs in ncommand.c, but that file is already 33292 bytes + * long. + */ +readrc(fd) + FILE *fd; +{ + char *bufptr, *buf; + size_t len; + int strlenbuf; + + buf = NULL; + strlenbuf = 0; + while (bufptr = fgetln(fd, &len)) { + if (!len) + continue; /* ??? */ + if (*bufptr == '#') + continue; /* skip comments */ + if (!(buf = reallocf(buf, strlenbuf + len + 1))) { + SETERR(E_MALLOC); + handle_error(); + if (strlenbuf + len < 1024) + return; /* major memory shortage... */ + continue; + } + memcpy (buf + strlenbuf, bufptr, len); + buf[len + strlenbuf] = '\0'; + if (len > 1 && buf[strlenbuf + len - 2] == '\\') { + /* line continuation */ + buf[strlenbuf + len - 2] = '\0'; + strlenbuf = strlen(buf); + continue; + } + if (buf[len + strlenbuf - 1] == '\n') + buf[len + strlenbuf - 1] = '\0'; + if (command(buf)) + handle_error(); + free(buf); + buf = NULL; + strlenbuf = 0; + } +} + +/* + * Read from the NUL-terminated cookie. Non-reentrant: keeps a static pointer + * to the current position in the cookie. Used for funopen(). + */ +static int +str_read(cookie, buf, len) + void *cookie; + char *buf; + size_t len; +{ + static char *curpos; + static int cooklen; + static char *lastcook; + + if (lastcook != cookie) { + /* begin working on a new cookie */ + curpos = cookie; + lastcook = cookie; + cooklen = strlen(cookie); + } + + if (curpos + len > lastcook + cooklen) { + ssize_t r; + memcpy(buf, curpos, r = (cooklen - (curpos - lastcook))); + curpos = cookie + cooklen; + return (int) r; + } else { + memcpy(buf, curpos, len); + curpos += len; + return (int) len; + } +} diff --git a/usr.bin/more/more.1 b/usr.bin/more/more.1 index 09bd03066c89..e0f9db7a0bac 100644 --- a/usr.bin/more/more.1 +++ b/usr.bin/more/more.1 @@ -225,8 +225,10 @@ which does NOT contain the pattern. .It Ic \&?\&! Ns Ar pattern Like ?, but the search is for the N-th line which does NOT contain the pattern. -.It Ic n -Repeat previous search, for N-th line containing the last pattern +.It Ic n No and Ic N +Repeat previous search, +in same or opposite direction respectively, +for N-th line containing the last pattern (or .Tn NOT containing the last pattern, if the previous search @@ -237,11 +239,11 @@ If the filename is missing, the "current" file (see the N and P commands below) from the list of files in the command line is re-examined. If the filename is a pound sign (#), the previously examined file is re-examined. -.It Ic N No or Ic \&:n +.It Ic \&:n Examine the next file (from the list of files given in the command line). If a number N is specified (not to be confused with the command N), the N-th next file is examined. -.It Ic P No or Ic \&:p +.It Ic \&:p Examine the previous file. If a number N is specified, the N-th previous file is examined. .It Ic \&:t diff --git a/usr.bin/more/more.help b/usr.bin/more/more.help index eb78cb4af0c8..1d5b1fb29502 100644 --- a/usr.bin/more/more.help +++ b/usr.bin/more/more.help @@ -25,11 +25,13 @@ ?pattern * Search backward for N-th line containing the pattern. ?!pattern * Search backward for N-th line NOT containing the pattern. n * Repeat previous search (for N-th occurence). + N * Repeat previous search (for N-th occurence), switching + the direction of the search. :a Display the list of files. E [file] Examine a new file. - :n, N * Examine the next file. - :p, P * Examine the previous file. + :n * Examine the next file. + :p * Examine the previous file. :t [tag] Examine the tag. t, T Move forward or backward N tags in the gtags queue. v Run an editor on the current file. diff --git a/usr.bin/more/most.morerc b/usr.bin/more/most.morerc new file mode 100644 index 000000000000..781f73597b8b --- /dev/null +++ b/usr.bin/more/most.morerc @@ -0,0 +1,86 @@ +# +# This sample .morerc causes more to emulate the default most(1) +# keys, in so far as more(1) is capable (which is not very far at the +# moment). +# +# $FreeBSD$ +# +# BUGS: It's extremely unlikely that more(1) will ever support multiple +# windows as most(1) does. Multiple windows is an editor function, +# isn't it? +# + +macro 1 "" 'forw (${_sc_height} * ${number})' +macro 1 v 'forw ${number}' +macro 1 V 'forw ${number}' +macro 1 "" 'forw ${number}' +macro 1 "" 'back ${number}' +macro 1 ^ 'back ${number}' +macro 0 T 'goline 1' +macro 0 "\e<" 'goline 1' +macro 0 B 'goend' +macro 0 "\e>" 'goend' +# Doesn't match most(1) documentation +macro 1 "\e[C" 'rscroll ${number}' +macro 1 "\t" 'rscroll (60 * ${number})' +macro 1 > 'rscroll (60 * ${number})' +# Doesn't match most(1) documentation +macro 1 "\e[D" 'lscroll ${number}' +# Doesn't really do much, being ^B and all... +macro 1 "" 'lscroll (60 * ${number})' +macro 1 < 'lscroll (60 * ${number})' +macro 1 u 'back (${_sc_height} * ${number})' +macro 1 U 'back (${_sc_height} * ${number})' +macro 1 "" 'back (${_sc_height} * ${number})' +macro 1 "" 'back (${_sc_height} * ${number})' +macro 0 R 'repaint' +macro 0 r 'repaint' +macro 0 "" 'repaint' +macro 0 j 'condition (${number} == 0); \ + error "prompt for line-number not supported"; \ + condition (${number} != 0); \ + goline ${number}; \ + condition true;' +macro 0 J 'condition (${number} == 0); \ + error "prompt for line-number not supported"; \ + condition (${number} != 0); \ + goline ${number}; \ + condition true;' +macro 0 g 'condition (${number} == 0); \ + error "prompt for line-number not supported"; \ + condition (${number} != 0); \ + goline ${number}; \ + condition true;' +macro 0 G 'condition (${number} == 0); \ + error "prompt for line-number not supported"; \ + condition (${number} != 0); \ + goline ${number}; \ + condition true;' +macro 0 % 'condition (${number} == 0); \ + error "prompt for percent not supported"; \ + condition_toggle; \ + gopercent ${number}; \ + condition true;' +macro 0 q 'quit' +macro 0 Q 'quit' +macro 0 " E" 'quit' +macro 0 h 'help' +macro 0 H 'help' +macro 0 "" 'help' +macro 0 "\e[N" 'help' +macro 1 f 'magicasksearch forw ${number}' +macro 1 / 'magicasksearch forw ${number}' +macro 1 "" 'magicasksearch forw ${number}' +macro 1 ? 'magicasksearch back ${number}' +macro 1 n 'research ${_ls_direction_n} ${number}' +macro 1 N 'research ${_ls_direction_n} ${number}' +# The mark stuff could be more effeciently done with a temp var, of course... :) +# These are nice enough that I'm tempted to bring them into default.morerc +macro 0 m 'setmark z' +macro 0 " M" 'setmark z' +macro 0 "." 'setmark z' +macro 0 "" 'setmark y; gomark z; setmark x; gomark y; setmark z; gomark x;' +macro 0 "," 'setmark y; gomark z; setmark x; gomark y; setmark z; gomark x;' +macro 0 e 'edit' +macro 0 E 'edit' +macro 0 :n 'file next ${number}' diff --git a/usr.bin/more/ncommand.c b/usr.bin/more/ncommand.c new file mode 100644 index 000000000000..82032934278e --- /dev/null +++ b/usr.bin/more/ncommand.c @@ -0,0 +1,1452 @@ +/*- + * Copyright (c) 1999 Timmy M. Vanderhoek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef lint +static const char rcsid[] = + "$Id$"; +#endif /* not lint */ + +/* + * These functions handle evaluation of primitive commands. In general, + * commands either come from macro.h as it expands user input, or + * directly from a .morerc file (in which case only a limited set of + * commands is valid. + * + * Commands are matched by command() against a command table. The rest + * of the command line string passed to command() is then passed to a + * function corresponding to the given command. The specific command + * function evaluates the remainder of the command string with the help + * of getstr() and getnumb(), both of which also handle variable expansion + * into a single word. It may in the future be desirable to add a special + * getsstring(), get-search-string, function. Specific command functions + * should not try grokking the command string by themselves. + * + * A command and its arguments are terminated by either a NUL or a ';'. + * This is recognized by both getstr() and getint(). Specific command + * functions return a pointer to the end of the command (and its arguments) + * thus allowing command() to accept commands that are chained together + * by semicolons. If a specific command fails it returns NULL preventing + * any proceeding commands (chained together with ';') from being parsed. + * This can be considered as a feature. + * + * All variable-access functions and variable state are internal to + * ncommand.c. The sole exceptions are setvar() and setvari(). + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "less.h" +#include "pathnames.h" + +static getint(), getstr_free(); +static void **getstr_raisectxt(); + +/* The internal command table. */ + +static const char *cscroll(), *cquit(), *cerror(), *ceval(), *cset(), + *cflush(), *cmacro(), *caskfile(), *cusercom(), + *ctags(), *chscroll(), *cgomisc(), *cgoend(), *csearch(), + *cstat(), *cdeftog(), *ccondition(), *chelp(), *cfile(), + *cfile_list(), *cedit(), *cmark(), *creadrc(); + +/* An enum identifying each command */ +enum cident { + DEFTOG, /* Initialize toggle values */ + EVAL, /* Evaluate a subexpression */ + SET, /* Set a variable */ + MACRO, /* Create a new macro */ + ERROR, /* Print a notification message */ + CONDITION, /* Condition evaluation of (almost) _all_ commands */ + CONDITION_N, /* CONDITION with an inverse truth table */ + CONDITION_TOGGLE,/* Switch to the reverse sense of the last condition */ + USERCOM, /* Get the user to type in a direct command */ + READRC, /* Read-in a named rc file */ + QUIT, /* Quit */ + HELP, /* Help */ + FLUSH, /* Flush file buffer and friends */ + REPAINT, /* Redraw the screen (useful if it got trashed) */ + FORW_SCROLL, /* Scroll forward N lines */ + BACK_SCROLL, /* Scroll backward N lines */ + FORW, /* Jump or scroll forward N lines */ + BACK, /* Jump or scroll backwards N lines */ + LSCROLL, /* Scroll horizontally leftwards */ + RSCROLL, /* Scroll horizontally to the right */ + GOLINE, /* Goto line number N */ + GOPERCENT, /* Goto percent N of the file */ + GOEND, /* Goto the end of the file */ + EDIT, /* Edit the current file, using getenv(EDITOR) */ + ASKFILE, /* Ask for a different, new file */ + CFILE, /* Page/view the N'th next or prev file */ + FILE_LIST, /* List the files that CFILE moves around in */ + STAT, /* List detailed file statistics in prompt */ + MAGICASKSEARCH, /* Ask for a regexp search string */ + SEARCH, /* Search for a regexp */ + RESEARCH, /* Search for the next N'th occurrence */ + SETMARK, /* Set a bookmark to the current position */ + GOMARK, /* Goto a previously set bookmark */ + ASKFTAG, /* Ask for a tag to goto */ + NEXTFTAG, /* Move forward N in the tag queue */ + PREVFTAG, /* Move backwards N in the tag queue */ +}; + +static struct ctable { + const char *cname; + enum cident cident; + const char * (*cfunc)(enum cident, const char *args); +} ctable[] = { + { "deftog", DEFTOG, cdeftog }, + { "eval", EVAL, ceval }, + { "set", SET, cset }, + { "macro", MACRO, cmacro }, + { "error", ERROR, cerror }, + { "condition", CONDITION, ccondition }, + { "condition_!", CONDITION_N, ccondition }, + { "condition_toggle", CONDITION_TOGGLE, ccondition }, + { "condition_else", CONDITION_TOGGLE, ccondition }, + { "usercom", USERCOM, cusercom }, + { "readrc", READRC, creadrc }, + { "quit", QUIT, cquit }, + { "help", HELP, chelp }, + { "flush", FLUSH, cflush }, + { "repaint", REPAINT, cflush }, + { "forw_scroll", FORW_SCROLL, cscroll }, + { "back_scroll", BACK_SCROLL, cscroll }, + { "forw", FORW, cscroll }, + { "back", BACK, cscroll }, + { "rscroll", RSCROLL, chscroll }, + { "lscroll", LSCROLL, chscroll }, + { "goline", GOLINE, cgomisc }, + { "gopercent", GOPERCENT, cgomisc }, + { "goend", GOEND, cgoend }, + { "edit", EDIT, cedit }, + { "askfile", ASKFILE, caskfile }, + { "file", CFILE, cfile }, + { "file_list", FILE_LIST, cfile_list }, + { "stat", STAT, cstat }, + { "magicasksearch", MAGICASKSEARCH, csearch }, + { "search", SEARCH, csearch }, + { "research", RESEARCH, csearch }, + { "setmark", SETMARK, cmark }, + { "gomark", GOMARK, cmark }, + { "asktag", ASKFTAG, ctags }, + { "nexttag", NEXTFTAG, ctags }, + { "prevtag", PREVFTAG, ctags }, +}; + + +/* I believe this is just for cosmetic purposes. */ +#define CMD_EXEC lower_left(); flush() + + +/* + * Prototypes are for people who can't program. + */ + + +/* + * The main command string evaluator. Returns -1 if an error occurred + * in the command or in executing the command, returns 0 otherwise. If an + * error occurs while evaluating a command line containing multiple commands, + * commands after the error are not processed. Multiple commands may be + * separated by ';' or '\n'. (Multiple commands may also be separated by + * a ' ', but this is really a bug...) + */ +int +command(line) + const char *line; +{ + struct ctable *i; + +donextcommand: + + while (isspace(*line) || *line == ';' || *line == '\n') line++; + if (!*line) + return 0; + + for (i = ctable; i != ctable + sizeof(ctable) / sizeof(struct ctable); + i++) { + if (!strncmp(i->cname, line, strlen(i->cname)) && + (line[strlen(i->cname)] == ' ' || + line[strlen(i->cname)] == ';' || + line[strlen(i->cname)] == '\0')) { + /* Found a match! */ + void **ctxt; + CMD_EXEC; + ctxt = getstr_raisectxt(); + line = i->cfunc (i->cident, line + strlen(i->cname)); + getstr_free(ctxt); + if (!line) + return -1; /* error evaluating command */ + goto donextcommand; + } + } + + SETERRSTR(E_BOGCOM, "invalid command: ``%s''", line); + (void) command("condition true"); + return -1; +} + + +/***************************************************************************** + * + * Functions to help specific command functions to parse their arguments. + * + * The three functions here, getstr(), getint(), and gettog() could in theory + * have vastly different concepts of what a number is, and what a string is, + * etc., but in practice they don't. + */ + +static char *readvar(); + +#define NCTXTS 30 +void *getstr_ctxts[NCTXTS]; /* could easily be made dynamic... */ +void **getstr_curctxt = getstr_ctxts; + +/* + * Read a single argument string from a command string. This understands + * $variables, "double quotes", 'single quotes', and backslash escapes + * for \\, \$, \n, \e, \t, and \" (the latter only inside double quotes). A + * string may be delimited by double quotes or spaces, not both (duh). It + * may be worthwhile to add another quotation style in which arithmetic + * expressions are expanded. Currently an arithmetic expression is expanded + * iff it is the only component of the string. + * + * Returns a pointer to the beginning of the string or NULL if it was unable to + * read a string. The line is modified to point somewhere between the end of + * the command argument just read-in and the beginning of the next command + * argument (if any). The returned pointer will be free()'d by calling + * getstr_free(). + */ +static char * +getstr(line) + char **line; /* Where to look for the return string */ +{ + int doquotes = 0; /* Doing a double-quote string */ + char *retr; + + if (getstr_curctxt - getstr_ctxts == NCTXTS) { + SETERRSTR(E_COMPLIM, + "compile-time limit exceeded: command contexts"); + return NULL; /* wouldn't be able to register return pointer */ + } + + while (isspace(**line)) (*line)++; + if (**line == '\'') { + /* Read until closing quote or '\0'. */ + char *nextw = retr = malloc(1); + char *c = ++(*line); + int l; + for (; *c; c++) { + if (*c == '\'') { + if (c[-1] == '\\') { + nextw[-1] = '\''; + continue; + } else { + *nextw = '\0'; + *line = c + 1; + *getstr_curctxt = retr; + getstr_curctxt++; + return retr; + } + } + l = nextw - retr; + /* XXX How many realloc()'s can you make per second? */ + if (!(retr = reallocf(retr, c - *line + 250))) { + SETERR (E_MALLOC); + return NULL; + } + nextw = retr + l; + *nextw = *c; + nextw++; + } + SETERR(E_CANTPARSE); + return NULL; + } + if (**line == '"') { + doquotes = 1; + (*line)++; + } + if (**line == '(') { + /* An arithmetic expression instead of a string... Well, I + * guess this is valid. See comment leading this function. */ + int n; + if (getint(&n, line)) + return NULL; + retr = NULL; + asprintf(&retr, "%d", n); + if (!retr) + SETERR (E_MALLOC); + *getstr_curctxt = retr; + getstr_curctxt++; + return retr; + } + + if (!FMALLOC(1, retr)) + return NULL; + *retr = '\0'; + for (;;) { + char *c, hack[2]; + + switch (**line) { + case '\\': + switch (*(*line + 1)) { + case '\\': case '$': case '\'': + case 't': case ' ': case ';': + hack[0] = *(*line + 1); + hack[1] = '\0'; + c = hack; + (*line) += 2; + break; + case 'n': + c = "\n"; + (*line) += 2; + break; + case 'e': + c = "\e"; + (*line) += 2; + break; + case '"': + if (doquotes) { + c = "\""; + (*line) += 2; + break; + } else + ; /* fallthrough */ + default: + c = "\\"; + (*line)++; + break; + } + break; + case '$': + (*line)++; + if (!(c = readvar(line))) { + free (retr); + return NULL; + } + break; + case ' ': case '\t': case ';': + if (!doquotes) { + doquotes = 1; + case '"': + if (doquotes) { + /* The end of the string */ + (*line)++; + case '\0': + *getstr_curctxt = retr; + getstr_curctxt++; + return retr; + } + } + /* fallthrough */ + default: + hack[0] = **line; + hack[1] = '\0'; + c = hack; + (*line)++; + break; + } + + retr = reallocf(retr, strlen(retr) + strlen(c) + 1); + if (!retr) { + SETERR (E_MALLOC); + return NULL; + } + strcat(retr, c); + } +} + +/* + * Returns a new context that should be passed to getstr_free() so that + * getstr_free() only free()'s memory from that particular context. + */ +static void ** +getstr_raisectxt() +{ + return getstr_curctxt; +} + +/* + * Calls free() on all memory from context or higher. + */ +static +getstr_free(context) + void **context; +{ + while (getstr_curctxt != context) { + getstr_curctxt--; + free (*getstr_curctxt); + } +} + +/* + * Reads an integer value from a command string. Typed numbers must be + * in base10. If a '(' is found as the first character of the integer value, + * then getint() will read until a closing ')' unless interupted by an + * end-of-command marker (error). The parentheses are expected to contain a + * simple arithmetic statement involving only one '*', '/', etc. operation. The + * rightmost digit or the closing parenthesis should be followed by either a + * space or an end-of-command marker. + * + * Returns 0 on success, -1 on failure. The line will be modified to just + * after the last piece of text parsed. + * + * XXX We may add support for negative numbers, someday... + */ +static int +getint(numb, line) + long *numb; /* The read-in number is returned through this */ + char **line; /* The command line from which to read numb */ +{ + long n; + int j; + char *p, *t; + + while (isspace(**line)) (*line)++; + + switch (**line) { + case '(': + (*line)++; + if (getint(numb, line)) + return -1; + while (isspace(**line)) (*line)++; + j = **line; + (*line)++; + if (j == ')') + return 0; + if (**line == '=' && (j == '!' || j == '=') + || j == '&' && **line == '&' || j == '|' && **line == '|') + j = (j << 8) + *((*line)++); + if (getint(&n, line)) + return -1; + while (isspace(**line)) (*line)++; + if (**line != ')') { + SETERRSTR (E_BADMATH, + "missing arithmetic close parenthesis"); + return -1; + } else + (*line)++; + switch (j) { + case ('!' << 8) + '=': + *numb = *numb != n; + return 0; + case ('=' << 8) + '=': + *numb = *numb == n; + return 0; + case ('&' << 8) + '&': + *numb = *numb && n; + return 0; + case ('|' << 8) + '|': + *numb = *numb || n; + return 0; + case '+': + *numb += n; + return 0; + case '-': + *numb -= n; + return 0; + case '*': + *numb *= n; + return 0; + case '/': + if (n == 0) + *numb = 1; + else + *numb /= n; + return 0; + default: + SETERRSTR (E_BADMATH, + "bad arithmetic operator: ``%c''", j); + return -1; + } + case '$': + t = (*line)++; + if (!(p = readvar(line))) + return -1; + if (!isdigit(*p)) { + SETERRSTR (E_BADMATH, + "non-number found (``%s'') " + "after expanding variable at ``%s''", p, t); + return -1; + } + *numb = atol(p); + return 0; + case '9': case '0': case '8': case '1': case '7': case '2': case '6': + case '3': case '5': case '4': + *numb = atol(*line); + while (isdigit(**line)) (*line)++; + return 0; + case '"': case '\'': + /* Uh-oh. It's really a string. We'll go through getstr() + * and hope for the best, but this isn't looking good. */ + if (!(p = getstr(line))) + return -1; + *numb = atol(p); + return 0; + default: + SETERRSTR (E_BADMATH, + "non-number found, number expected, before parsing ``%s''", + *line); + return -1; + } +} + +/* + * Read an argument from the command string and match that argument against + * a series of legitimate values. For example, + * + * command <> + * + * This command by be given to the command() processor as a variant of either + * "command opt1" or "command 4", both of which will cause this function to + * return the value 1. This function returns -1 on failure. + * + * Note that an option (eg. "opt1") must _not_ start with a digit!! + */ +static int +gettog(const char **line, int nopts, ...) +{ + char *str; + int n; + va_list opts; + + if (!(str = getstr(line))) + return -1; + + if (isdigit(*str)) { + n = atol(str) % nopts; + return n; + } + + va_start(opts, nopts); + for (n=0; n < nopts; n++) { + if (!strcasecmp(str, va_arg(opts, const char *))) { + va_end(opts); + return n; + } + } + va_end(opts); + SETERR (E_NOTOG); /* XXX would be nice to list valid toggles... */ + return -1; +} + +/* + * A companion function for gettog(). Example, + * + * optnumb = gettog(&args, 3, "opt1", "opt2", "opt3"); + * settog("_lastoptnumb", optnumb, "opt1", "opt2", "opt3"); + * + * And the variable named _lastoptnumb_s will be set to one of "opt1", "opt2", + * or "opt3" as per the value of optnumb. The variable _lastoptnumb_n will + * also be set to a corresponding value. The optnumb argument had better + * be within the correct range (between 0 and 2 in the above example)!! + */ +settog(const char *varname, int optval, int nargs, ...) +{ + va_list opts; + char *s; + int optval_orig = optval; + + assert(optval < nargs); assert(optval >= 0); + if (!(s = malloc(strlen(varname) + 3))) + return; + strcpy (s, varname); + va_start(opts, nargs); + for (; optval; optval--) va_arg(opts, const char *); + s[strlen(varname)] = '_'; + s[strlen(varname) + 1] = 's'; + s[strlen(varname) + 2] = '\0'; + (void) setvar(s, va_arg(opts, const char *)); + s[strlen(varname) + 1] = 'n'; + (void) setvari(s, (long) optval_orig); + clear_error(); + va_end(opts); + free(s); +} + +/* + * Read {text} and return the string associated with the variable named + * <>. Returns NULL on failure. + */ +static char * +readvar(line) + char **line; +{ + int vlength; + char *vstart; + static char *getvar(); + + if (**line != '{') { + SETERR (E_BADVAR); + return NULL; + } + (*line)++; + for (vlength = 0, vstart = *line; **line && + (isalpha(**line) || **line == '_'); (*line)++) + vlength++; + if (**line != '}' || vlength == 0) { + SETERRSTR (E_BADVAR, + "bad character ``%c'' in variable ``%.*s''", **line, + vlength, vstart); + return NULL; + } + (*line)++; + return getvar(vstart, vlength); +} + + +/***************************************************************************** + * + * Track variables. + * + */ + +static struct vble { + struct vble *next; + char *name; + char *value; +} *vble_l; /* linked-list of existing variables */ + +/* + * Return a pointer to the string that variable var represents. Returns + * NULL if a match could not be found and sets erreur. + */ +static const char * +getvar(var, len) + char *var; + int len; /* strncmp(var, varmatch, len); is used to match variables */ +{ + struct vble *i; + + for (i = vble_l; i; i = i->next) { + if (!strncasecmp (i->name, var, len)) + return i->value; + } + + SETERRSTR (E_BADVAR, "variable ``%.*s'' not set", len, var); + return NULL; +} + +/* + * Set variable var to val. Returns -1 on failure, 0 on success. + */ +int +setvar(var, val) + char *var; /* variable to set */ + char *val; /* value to set variable to */ +{ + struct vble *i, *last; + char *var_n, *val_n; + char *c; + + for (c = var; *c && (isalpha(*c) || *c == '_'); c++) ; + if (*c) { + SETERRSTR (E_BADVAR, + "bad character ``%c'' in variable ``%s''", *c, var); + return -1; + } + + for (i = vble_l; i; last = i, i = i->next) { + if (!strcasecmp (i->name, var)) { + if (!FMALLOC(strlen(val) + 1, val_n)) + return -1; + free(i->value); + i->value = val_n; + strcpy(i->value, val); + return 0; + } + } + + /* Need to add another variable to the list vble_l */ + if (!FMALLOC(strlen(var) + 1, var_n)) + return -1; + if (!FMALLOC(strlen(val) + 1, val_n)) + return -1; + if (!vble_l) { + if (!FMALLOC(sizeof(struct vble), vble_l)) + return -1; + i = vble_l; + } else { + if (!FMALLOC(sizeof(struct vble), last->next)) + return -1; + i = last->next; + } + i->next = NULL; + i->name = var_n; strcpy(i->name, var); + i->value = val_n; strcpy(i->value, val); + return 0; +} + +/* + * Set or reset, as appropriate, variable var to val. + */ +int +setvari(var, val) + const char *var; + long val; +{ + char n[21]; /* XXX */ + snprintf(n, sizeof(n), "%ld", val); + n[20] = '\0'; + setvar(var, n); +} + + +/***************************************************************************** + * + * Specific command functions. These aren't actually individual functions, + * since using a gigantic switch statement is faster to type, but they + * pretend to be individual functions. + * + */ + +int condition_eval = 1; /* false if we just parse commands, but do nothing */ + +#define ARGSTR(v) do { \ + if (!((v) = getstr(&args))) return NULL; \ + } while (0) +#define ARGNUM(v) do { \ + if (getint(&(v), &args)) return NULL; \ + } while (0) +/* semi-gratuitous use of GNU cpp extension */ +#define ARGTOG(v, n, togs...) do { \ + if (((v) = gettog(&args, n, togs)) == -1) \ + return NULL; \ + } while (0) +#define ENDPARSE do { \ + if (!condition_eval) return args; \ + } while (0) + +/* + * deftog + * + * Set all toggle options to their default values, provided the toggle option + * is registered with this function. This command is meant to be used at the + * beginning of the startup command list. + */ +static const char * +cdeftog(cident, args) + enum cident cident; + const char *args; +{ + extern int horiz_off, wraplines; + + ENDPARSE; + settog("_stat", 1, 2, "on", "off"); + settog("_ls_direction", 0, 2, "forw", "back"); + settog("_ls_sense", 0, 2, "noinvert", "invert"); + setvari("_curhscroll", (long) horiz_off); + settog("_wraplines", wraplines, 2, "off", "on"); + setvar("_ls_regexp", ""); + /* + * not present: _file_direction + */ + return args; +} + +/* + * eval <> + * + * Passes string back into the command evaluator. + */ +static const char * +ceval(cident, args) + enum cident cident; + const char *args; +{ + const char *com; + + ARGSTR(com); /* The command line to evaluate */ + ENDPARSE; + + /* It's not clear what to do with the command() return code */ + (void) command(com); + + return args; +} + +/* + * set <> <> + * + * Sets variable variablename to string variablestring. + */ +static const char * +cset(cident, args) + enum cident cident; + const char *args; +{ + const char *str, *var; + + ARGSTR(var); /* name of variable to set */ + ARGSTR(str); /* value to set variable to */ + ENDPARSE; + + if (*var == '_') { + SETERRSTR (E_BADVAR, + "variables beginning with '_' are reserved"); + return NULL; + } + if (setvar(var, str)) + return NULL; + + return args; +} + +/* + * macro <> <> <> + * + * Associates the macro keys with command. + */ +static const char * +cmacro(cident, args) + enum cident cident; + const char *args; +{ + const char *keys, *com; + long num; + + ARGNUM(num); /* the default number N for this macro */ + ARGSTR(keys); /* string of keys representing a macro */ + ARGSTR(com); /* command line to associate with macro */ + ENDPARSE; + + if (setmacro(keys, com)) + return NULL; + if (setmacnumb(keys, num)) + return NULL; + + return args; +} + +/* + * error <> + * + * Prints a notification message. + */ +static const char * +cerror(cident, args) + enum cident cident; + const char *args; +{ + char *s; + + ARGSTR(s); /* error message */ + ENDPARSE; + error(s); + return args; +} + +/* + * condition <> + * condition_! <> + * + * If boolean is false, causes all commands except for other condition + * commands to be ignored. The <> may be specified as a number + * (in which case even numbers are true, odd numbers are false), or one + * of "on", "off", "true", and "false". + */ +static const char * +ccondition(cident, args) + enum cident cident; + const char *args; +{ + /* ENDPARSE; */ + + if (cident == CONDITION_TOGGLE) { + condition_eval = !condition_eval; + return args; + } + + switch (gettog(&args, 4, "off", "on", "false", "true")) { + case 0: case 2: + condition_eval = 0; + break; + case 1: case 3: + condition_eval = 1; + break; + case -1: + return NULL; + } + if (cident == CONDITION_N) + condition_eval = !condition_eval; + return args; +} + +/* + * usercom + * + * Accept a direct command from the user's terminal. + */ +static const char * +cusercom(cident, args) + enum cident cident; + const char *args; +{ + char buf[125]; /* XXX should avoid static buffer... */ + + ENDPARSE; + getinput("Command: ", buf, sizeof(buf)); + if (command(buf)) + return NULL; + + return args; +} + +/* + * readrc <> + * + * Read-in rc commands from the named file. + */ +static const char * +creadrc(cident, args) + enum cident cident; + const char *args; +{ + const char *file; + FILE *fd; + + ARGSTR(file); + ENDPARSE; + + if (!*file) + return args; + /* + * Should perhaps warn user if file perms or ownership look suspicious. + */ + fd = fopen(file, "r"); + if (!fd) { + SETERRSTR (E_NULL, "could not open file ``%s''", file); + return NULL; + } + readrc(fd); + fclose(fd); + + return args; +} + +/* + * quit + * + * Performs as advertised. + */ +static const char * +cquit(cident, args) + enum cident cident; + const char *args; +{ + ENDPARSE; + quit(); + return NULL; /* oh boy... */ +} + +/* + * help + * + * Doesn't do much. + */ +static const char * +chelp(cident, args) + enum cident cident; + const char *args; +{ + extern int ac, curr_ac; + extern char **av; + + ENDPARSE; + if (ac > 0 && !strcmp(_PATH_HELPFILE, av[curr_ac])) { + SETERRSTR(E_NULL, "already viewing help"); + return NULL; + } + help(); + return args; +} + +/* + * flush + * repaint + * + * Flushes the file buffer, provided we are not reading from a pipe. + * Frees any other memory that I can get my hands on from here. + */ +static const char * +cflush(cident, args) + enum cident cident; + const char *args; +{ + extern int ispipe; + + ENDPARSE; + if (cident == FLUSH && !ispipe) { + ch_init(0, 0); /* XXX should this be ch_init(ctags,0) */ + clr_linenum(); + } + repaint(); + + return args; +} + +/* + * forw_scroll <> + * back_scroll <> + * forw <> + * back <> + * + * Move forward number n lines. The _scroll variants force a scroll, the + * others may scroll or may just redraw the screen at the appropriate location, + * whichever is faster. + */ +static const char * +cscroll(cident, args) + enum cident cident; + char *args; +{ + long n; + char *retr; + + ARGNUM(n); /* number of lines to move by */ + ENDPARSE; + + switch (cident) { + case FORW_SCROLL: + forward(n, 0); + break; + case BACK_SCROLL: + backward(n, 0); + break; + case FORW: + forward(n, 1); + break; + case BACK: + backward(n, 1); + break; + } + + return args; +} + +/* + * rscroll <> + * lscroll <> + * + * Scroll left or right by n lines. + */ +static const char * +chscroll(cident, args) + enum cident cident; + const char *args; +{ + long n; + char *retr; + extern int horiz_off, wraplines; + + ARGNUM(n); /* Number of columns to scroll by */ + ENDPARSE; + + if (n == 0) + return args; + + switch (cident) { + case RSCROLL: + if (wraplines) { + wraplines = 0; + assert (horiz_off == 0); + } else { + horiz_off += n; + if (horiz_off < 0) + horiz_off = INT_MAX; /* disaster control */ + } + break; + case LSCROLL: + if (horiz_off != 0) { + horiz_off -= n; + if (horiz_off < 0) + horiz_off = 0; + } else + wraplines = 1; + break; + } + repaint(); /* screen_trashed = 1 */ + setvari("_curhscroll", (long) horiz_off); + settog("_wraplines", wraplines, 2, "off", "on"); + + return args; +} + +/* + * goline <> + * gopercent <> + * + * Goto the line numbered <>, if possible. Goto <> percent of + * the file. Whole-numbered percents only, of course. + */ +static const char * +cgomisc(cident, args) + enum cident cident; + const char *args; +{ + long n; + + ARGNUM(n); /* number N */ + ENDPARSE; + + switch (cident) { + case GOLINE: + jump_back(n); + break; + case GOPERCENT: + if (n > 100) + n = 100; + jump_percent(n); + break; + } + return args; +} + +/* + * goend + * + * Goto the end of the file. Future variation should include the GNU less(1)- + * style follow a-la tail(1). + */ +static const char * +cgoend(cident, args) + enum cident cident; + const char *args; +{ + ENDPARSE; + jump_forw(); + return args; +} + +/* + * edit + * + * Edits the current file with a word editor. This command is just begging + * to be extended to allow the user to specify an editor. Additionally, this + * would require some kind of getenv command or similar change. + */ +static const char * +cedit(cident, args) + enum cident cident; + const char *args; +{ + extern ispipe; + + ENDPARSE; + if (ispipe) { + SETERRSTR(E_NULL, "cannot edit standard input"); + return NULL; + } + editfile(); + /* + * XXX less-than brilliant things happen if the user while editing + * deletes a large section at the end of the file where we think we + * are currently viewing... + */ + ch_init(0, 0); /* Clear the internal file buffer */ + clr_linenum(); + return args; +} + +/* + * askfile + * + * Loads a new file. Queries the user for the name of the new file. + */ +static const char * +caskfile(cident, args) + enum cident cident; + const char *args; +{ + char buf[MAXPATHLEN + 1]; + + ENDPARSE; + getinput("Examine: ", buf, sizeof(buf)); + /* XXX should modify this() or edit() to handle lists of file, ie. + * the type of lists that I get if I try to glob("*") */ + (void)edit(glob(buf)); + + return args; +} + +/* + * file <> <> + * + * Loads the N'th next or previous file, typically from the list of files + * given on the command line. + */ +static const char * +cfile(cident, args) + enum cident cident; + const char *args; +{ + enum { FORW=0, BACK=1 } direction; + long N; + + ARGTOG(direction, 10, "next", "previous", "forward", "backward", + "forwards", "backwards", "next", "prev", "forw", "back"); + ARGNUM(N); + ENDPARSE; + direction %= 2; + + /* next_file() and prev_file() call error() directly (bad) */ + switch (direction) { + case FORW: + next_file(N); + break; + case BACK: + prev_file(N); + break; + } + settog("_file_direction", direction, 2, "next", "previous"); + return args; +} + +/* + * file_list + * + * Lists the files the "file next" and "file prev" are moving around in. + */ +static const char * +cfile_list(cident, args) + enum cident cident; + const char *args; +{ + ENDPARSE; + showlist(); + repaint(); /* screen_trashed = 1; */ + return args; +} + +/* + * stat <> + * + * Display the detailed statistics as part of the prompt. The toggle option + * variable is called _stat (giving ${_stat_s} and ${_stat_n}). + */ +static const char * +cstat(cident, args) + enum cident cident; + const char *args; +{ + int onoff; + + ARGTOG(onoff, 2, "on", "off"); + ENDPARSE; + statprompt(onoff); + settog("_stat", onoff, 2, "on", "off"); + return args; +} + +/* + * magicasksearch <> <> + * search <> <> <<> > + * research <> <> + * + * Arguments specifying an option (ie. <> and <> + * may be specified either as text (eg. "forw"), or as a number, in which case + * even numbers specify the former setting and odd numbers the latter setting. + * + * The magicasksearch will ask the user for a regexp and intuit whether they + * want to invert the sense of matching or not: if the first character of the + * regexp is a '!', it is removed and the sense is inverted. + * + * The toggle options are called _ls_direction and _ls_sense. In addition, + * ${_ls_regexp} is set to the regexp used. These variables are only set + * when the search and magicsearch commands are used. + */ +static const char * +csearch(cident, args) + enum cident cident; + const char *args; +{ + char buf[100], *str; + enum { FORW=0, BACK=1 } direction; + static enum { NOINVERT=0, INVERT=1 } sense; + long N; + + ARGTOG(direction, 6, "forw", "back", "forward", "backward", + "forwards", "backwards"); + ARGNUM(N); + if (cident == SEARCH) { + ARGTOG(sense, 2, "noinvert", "invert"); + ARGSTR(str); + } + ENDPARSE; + direction %= 2; + + /* Get the search string, one way or another */ + switch (cident) { + case MAGICASKSEARCH: + biggetinputhack(); /* It's magic, boys */ + if (direction == FORW) + getinput("Search: /", buf, 2); + else + getinput("Search: ?", buf, 2); + switch (*buf) { + case '\0': + /* Cancelled */ + return args; + case '!': + /* Magic */ + if (direction == FORW) + getinput("Search: !/", buf, sizeof(buf)); + else + getinput("Search: !?", buf, sizeof(buf)); + sense = INVERT; + break; + default: + /* No magic */ + ungetcc(*buf); + if (direction == FORW) + getinput("Search: /", buf, sizeof(buf)); + else + getinput("Search: ?", buf, sizeof(buf)); + sense = NOINVERT; + break; + } + if (!*buf) + return args; + str = buf; + break; + case SEARCH: + break; + case RESEARCH: + str = NULL; + break; + } + + if (cident == SEARCH || cident == MAGICASKSEARCH) { + settog("_ls_direction", direction, 2, "forw", "back"); + settog("_ls_sense", sense, 2, "noinvert", "invert"); + setvar("_ls_regexp", str); + } + search(!direction, str, N, !sense); + return args; +} + +/* + * setmark <> + * gomark <> + * + * Set a marker at the current position, or goto a previously set marker. + * Character may be a-z, or '?' to ask the user to enter a character. The + * special mark '\'' may not be set, but may be the target of a goto. + */ +static const char * +cmark(cident, args) + enum cident cident; + const char *args; +{ + char smark[2]; + const char *mark; + + ARGSTR(mark); + ENDPARSE; + + /* gomark() and setmark() will further check mark's validity */ + if (!*mark || mark[1]) { + SETERRSTR(E_NULL, "bad mark character"); + return NULL; + } + + if (*mark == '?') { + biggetinputhack(); /* so getinput() returns after one char */ + switch (cident) { + case GOMARK: + getinput("goto mark: ", smark, sizeof smark); + break; + case SETMARK: + getinput("set mark: ", smark, sizeof smark); + break; + } + if (!*smark) + return args; + mark = smark; + } + + switch (cident) { + case GOMARK: + gomark(*mark); + break; + case SETMARK: + setmark(*mark); + break; + } + return args; +} + +/* + * asktag + * nexttag <> + * prevtag <> + * + * Asks the user for a tag, or moves around the tag queue. + */ +static const char * +ctags(cident, args) + enum cident cident; + const char *args; +{ + extern char *tagfile; /* XXX No reason for this to be a global... */ + long n; + + if (cident != ASKFTAG) + ARGNUM(n); + ENDPARSE; + + if (cident == ASKFTAG) { + char buf[100]; /* XXX should do something else... */ + getinput("Tag: ", buf, sizeof(buf)); + if (!*buf) + return args; + findtag(buf); + } else { + switch (cident) { + case NEXTFTAG: + nexttag(n); + break; + case PREVFTAG: + prevtag(n); + break; + } + } + + /* Load the tagfile and position ourselves. */ + if (tagfile == NULL) + return NULL; + if (edit(tagfile)) + tagsearch(); + return args; /* tag stuff still calls error() on its own */ +} diff --git a/usr.bin/more/option.c b/usr.bin/more/option.c index 53f332b9e6ce..1bb2202a94e9 100644 --- a/usr.bin/more/option.c +++ b/usr.bin/more/option.c @@ -77,6 +77,16 @@ option(argc, argv) while ((ch = getopt(argc, argv, "/:ceinst:ux:f")) != -1) switch((char)ch) { case '/': + /* + * Might be interesting to make this option search + * through the whole list of files on the command line + * until a match is found. Prior to this commit adding + * the new comand interpreter, it would sort-of do + * this, provided all the files listed on the command + * line were of length zero bytes (well, with the + * exception of the file actually containing a match, + * I suppose). + */ firstsearch = optarg; break; case 'c': diff --git a/usr.bin/more/output.c b/usr.bin/more/output.c index 337a294e6b9a..cf07f09b225f 100644 --- a/usr.bin/more/output.c +++ b/usr.bin/more/output.c @@ -51,8 +51,6 @@ static const char rcsid[] = #include "less.h" -int errmsgs; /* Count of messages displayed by error() */ - extern int bs_mode; extern int sigs; extern int sc_width, sc_height; @@ -61,7 +59,6 @@ extern int so_width, se_width; extern int bo_width, be_width; extern int tabstop; extern int screen_trashed; -extern int any_display; extern char *line; extern int horiz_off; extern int mode_flags; @@ -121,7 +118,7 @@ markup(ent_ul, ent_bo) * UL_CHAR, UE_CHAR, BO_CHAR, BE_CHAR markups. */ #define MAYPUTCHR(char) \ - if (column >= eff_horiz_off) { \ + if (column >= horiz_off) { \ column += markup(&ent_ul, &ent_bo); \ putchr(char); \ } @@ -133,7 +130,6 @@ put_line() register int column; extern int auto_wrap, ignaw; int ent_ul, ent_bo; /* enter or exit ul|bo mode for next char */ - int eff_horiz_off; if (sigs) { @@ -144,11 +140,6 @@ put_line() return; } - if (horiz_off == NO_HORIZ_OFF) - eff_horiz_off = 0; - else - eff_horiz_off = horiz_off; - if (line == NULL) line = ""; @@ -198,11 +189,11 @@ put_line() case '\b': /* * column must be at least one greater than - * eff_horiz_off (ie. we must be in the second or + * horiz_off (ie. we must be in the second or * beyond screen column) or we'll just end-up * backspacing up to the previous line. */ - if (column > eff_horiz_off) { + if (column > horiz_off) { column += markup(&ent_ul, &ent_bo); putbs(); column--; @@ -234,11 +225,11 @@ put_line() column++; } } - if (column == sc_width + eff_horiz_off && mode_flags) + if (column == sc_width + horiz_off && mode_flags) last_pos_highlighted = 1; } column += markup(&ent_ul, &ent_bo); - if (column < sc_width + eff_horiz_off || !auto_wrap || ignaw) + if (column < sc_width + horiz_off || !auto_wrap || ignaw) putchr('\n'); } @@ -290,65 +281,28 @@ putstr(s) putchr(*s++); } -int cmdstack; -static char return_to_continue[] = "(press RETURN)"; - /* - * Output a message in the lower left corner of the screen - * and wait for carriage return. + * Output a string, expanding control characters into printable sequences. + * Returns the number of characters printed. */ -error(s) +int +putxstr(s) char *s; { - int ch; + int c; + int retr = 0; - ++errmsgs; - if (!any_display) { - /* - * Nothing has been displayed yet. Output this message on - * error output (file descriptor 2) and don't wait for a - * keystroke to continue. - * - * This has the desirable effect of producing all error - * messages on error output if standard output is directed - * to a file. It also does the same if we never produce - * any real output; for example, if the input file(s) cannot - * be opened. If we do eventually produce output, code in - * edit() makes sure these messages can be seen before they - * are overwritten or scrolled away. - */ - (void)write(2, s, strlen(s)); - (void)write(2, "\n", 1); - return; + for (; c = *s; s++) { + if (CONTROL_CHAR(c)) { + putchr('^'); + retr++; + c &= ~0200; + c = CARAT_CHAR(c); + } + putchr(c); } - lower_left(); - clear_eol(); - so_enter(); - if (s) { - putstr(s); - putstr(" "); - } - putstr(return_to_continue); - so_exit(); - - if ((ch = getchr()) != '\n') { - /* XXX hardcoded */ - if (ch == 'q') - quit(); - cmdstack = ch; - } - lower_left(); - - if ((s==NULL)?0:(strlen(s)) + sizeof(return_to_continue) + - so_width + se_width + 1 > sc_width) - /* - * Printing the message has probably scrolled the screen. - * {{ Unless the terminal doesn't have auto margins, - * in which case we just hammered on the right margin. }} - */ - repaint(); - flush(); + return(retr); } static char intr_to_abort[] = "... (interrupt to abort)"; diff --git a/usr.bin/more/pathnames.h b/usr.bin/more/pathnames.h index c564360417c8..647ade649252 100644 --- a/usr.bin/more/pathnames.h +++ b/usr.bin/more/pathnames.h @@ -31,8 +31,14 @@ * SUCH DAMAGE. * * @(#)pathnames.h 8.1 (Berkeley) 6/6/93 + * + * $FreeBSD$ */ #include #define _PATH_HELPFILE "/usr/share/misc/more.help" +#define _PATH_RC ".morerc" +#define _PATH_DEFRC ".defmorerc" +#define _PATH_SYSMORERC "/etc/dot.morerc" +/* Should have a /etc/dot.defmorerc, too... */ diff --git a/usr.bin/more/prim.c b/usr.bin/more/prim.c index 03fcad8d408e..eb76f95676b5 100644 --- a/usr.bin/more/prim.c +++ b/usr.bin/more/prim.c @@ -313,7 +313,7 @@ prepaint(pos) off_t pos; { hit_eof = 0; - forw(sc_height-1, pos, 0); + forw(sc_height - 1, pos, 0); screen_trashed = 0; } @@ -581,26 +581,18 @@ gomark(c) new_horiz_off = marks[c-'a'].horiz_off; } - /* Try to be nice about changing the horizontal scroll */ - if (!(horiz_off == NO_HORIZ_OFF && new_horiz_off <= sc_width)) { + /* Try to be nice about changing the horizontal scroll and wrapping */ + if (new_horiz_off > sc_width / 3 + horiz_off) { /* - * We're going to have to change the horiz_off, even if - * it's currently set to NO_HORIZ_OFF: if we don't change - * horiz_off the bookmarked location won't show on the screen. + * We should change horiz_off: if we don't change horiz_off + * the bookmarked location won't be readily visible. */ - if (horiz_off != new_horiz_off) { - /* We'll need to repaint(), too... */ - horiz_off = new_horiz_off; - prepaint(pos); - } else { - /* No need to repaint. */ - jump_loc(pos); - } + horiz_off = new_horiz_off; + prepaint(pos); } else { /* - * The user doesn't want horizontal scrolling, and we can - * fortunately honour the bookmark request without doing - * any horizontal scrolling. + * We can honour the bookmark request without doing any + * horizontal scrolling. */ jump_loc(pos); } diff --git a/usr.bin/more/screen.c b/usr.bin/more/screen.c index 1d0f41ea8c79..970b46fcb3e0 100644 --- a/usr.bin/more/screen.c +++ b/usr.bin/more/screen.c @@ -102,7 +102,6 @@ int ignaw; /* Terminal ignores \n immediately after wrap */ int retain_below; /* Terminal retains text below the screen */ int erase_char, kill_char, werase_char; int sc_width, sc_height = -1; /* Height & width of screen */ -int sc_window = -1; /* window size for forward and backward */ int bo_width, be_width; /* Printing width of boldface sequences */ int ul_width, ue_width; /* Printing width of underline sequences */ int so_width, se_width; /* Printing width of standout sequences */ @@ -294,6 +293,8 @@ get_term() sc_width = tgetnum("co"); if (sc_width < 0) sc_width = 80; + (void) setvari("_sc_height", (long) sc_height - 1); + (void) setvari("_sc_width", (long) sc_width); auto_wrap = tgetflag("am"); ignaw = tgetflag("xn"); diff --git a/usr.bin/more/signal.c b/usr.bin/more/signal.c index b046d5231325..c8be3af17aac 100644 --- a/usr.bin/more/signal.c +++ b/usr.bin/more/signal.c @@ -65,7 +65,6 @@ extern int sc_width, sc_height; extern int screen_trashed; extern int lnloop; extern int linenums; -extern int scroll; extern volatile int reading; #ifdef SIGTSTP @@ -181,7 +180,8 @@ psignals() get_term(); if (sc_width != old_width || sc_height != old_height) { - scroll = (sc_height + 1) / 2; + (void) setvari("_sc_width", (long) sc_width); + (void) setvari("_sc_height", (long) sc_height - 1); screen_trashed = 1; } } diff --git a/usr.bin/more/ttyin.c b/usr.bin/more/ttyin.c index 51376cde1936..26f8f96e23a2 100644 --- a/usr.bin/more/ttyin.c +++ b/usr.bin/more/ttyin.c @@ -74,6 +74,6 @@ getchr() */ quit(); } - } while (result != 1); + } while (result != 1 || c == 0); return ((unsigned char)c); }