/*- * 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[] = "$FreeBSD$"; #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("_statprompt", 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 _statprompt (giving ${_statprompt_s} and * ${_statprompt_n}). */ static const char * cstat(cident, args) enum cident cident; const char *args; { int onoff; ARGTOG(onoff, 2, "on", "off"); ENDPARSE; statprompt(onoff); settog("_statprompt", 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. If the regexp * entered is null, then we will use ${_ls_regexp} (error if not set). * * 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 '!': /* 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)); case '\0': sense = NOINVERT; break; } 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"); if (*str) setvar("_ls_regexp", str); } /* * XXX Currently search() contains magic to deal with (*str=='\0'). * This magic should be moved into this function so that we can work * as described in the function comment header. */ 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 */ }