From 20d6e5fe6e93f71a1ae0ed8161a193c4631ae7cc Mon Sep 17 00:00:00 2001 From: Tim Vanderhoek Date: Fri, 3 Sep 1999 22:31:21 +0000 Subject: [PATCH] Thorough revamp of how input commands are processed. This allows customization of user keys (documentation pending). The only key whose semantics have changed is the capital 'N' key, which now performs a repeat-search in the opposite direction (just like in vi). This commit is a little bulkier than what I had originally planned. I'm not completely happy with the direction it went, but it's definately an improvement, and the alternative is to continue becoming irrelevant compared to GNU less. (Does anyone even _use_ /usr/bin/more these days?) --- usr.bin/more/Makefile | 30 +- usr.bin/more/command.c | 913 +++++++++++----------- usr.bin/more/decode.c | 223 ------ usr.bin/more/default.morerc | 134 ++++ usr.bin/more/input.c | 10 +- usr.bin/more/less.h | 108 ++- usr.bin/more/less.morerc | 61 ++ usr.bin/more/line.c | 13 +- usr.bin/more/macro.c | 292 +++++++ usr.bin/more/main.c | 168 +++- usr.bin/more/more.1 | 10 +- usr.bin/more/more.help | 6 +- usr.bin/more/most.morerc | 86 +++ usr.bin/more/ncommand.c | 1452 +++++++++++++++++++++++++++++++++++ usr.bin/more/option.c | 10 + usr.bin/more/output.c | 86 +-- usr.bin/more/pathnames.h | 6 + usr.bin/more/prim.c | 26 +- usr.bin/more/screen.c | 3 +- usr.bin/more/signal.c | 4 +- usr.bin/more/ttyin.c | 2 +- 21 files changed, 2801 insertions(+), 842 deletions(-) delete mode 100644 usr.bin/more/decode.c create mode 100644 usr.bin/more/default.morerc create mode 100644 usr.bin/more/less.morerc create mode 100644 usr.bin/more/macro.c create mode 100644 usr.bin/more/most.morerc create mode 100644 usr.bin/more/ncommand.c 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); }