1 /*-
2 * Copyright (c) 1999 Timmy M. Vanderhoek
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27
28 #ifndef lint
29 static const char rcsid[] =
30 "$FreeBSD: src/usr.bin/more/ncommand.c,v 1.11 2000/05/14 03:30:58 hoek Exp $";
31 #endif /* not lint */
32
33 /*
34 * These functions handle evaluation of primitive commands. In general,
35 * commands either come from macro.h as it expands user input, or
36 * directly from a .morerc file (in which case only a limited set of
37 * commands is valid.
38 *
39 * Commands are matched by command() against a command table. The rest
40 * of the command line string passed to command() is then passed to a
41 * function corresponding to the given command. The specific command
42 * function evaluates the remainder of the command string with the help
43 * of getstr() and getint(), both of which also handle variable expansion
44 * into a single word. Specific command functions should not try grokking
45 * the command string by themselves.
46 *
47 * A command and its arguments are terminated by either a NUL or a ';'.
48 * This is recognized by both getstr() and getint(). Specific command
49 * functions return a pointer to the end of the command (and its arguments)
50 * thus allowing command() to accept commands that are chained together
51 * by semicolons. If a specific command fails, it returns NULL preventing
52 * any proceeding commands (chained together with ';') from being parsed.
53 * This can be considered as a feature.
54 *
55 * All variable-access functions and variable state are internal to
56 * ncommand.c. The sole exceptions are setvar() and setvari().
57 */
58
59 #include <sys/param.h>
60
61 #include <assert.h>
62 #include <ctype.h>
63 #include <stdarg.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <stdio.h>
67
68 #include "less.h"
69 #include "pathnames.h"
70
71 static int getint __P((long *numb, const char **line));
72
73 static getstr_free();
74 static void **getstr_raisectxt();
75
76 /* The internal command table. */
77
78 static const char *cscroll(), *cquit(), *cerror(), *ceval(), *cset(),
79 *cflush(), *cmacro(), *caskfile(), *cusercom(),
80 *ctags(), *chscroll(), *cgomisc(), *cgoend(), *csearch(),
81 *cstat(), *cdeftog(), *ccondition(), *chelp(), *cfile(),
82 *cfile_list(), *cedit(), *cmark(), *creadrc(), *clistbmarks();
83
84 /* An enum identifying each command */
85 enum cident {
86 DEFTOG, /* Initialize toggle values */
87 EVAL, /* Evaluate a subexpression */
88 SET, /* Set a variable */
89 MACRO, /* Create a new macro */
90 ERROR, /* Print a notification message */
91 CONDITION, /* Condition evaluation of (almost) _all_ commands */
92 CONDITION_N, /* CONDITION with an inverse truth table */
93 CONDITION_TOGGLE,/* Switch to the reverse sense of the last condition */
94 USERCOM, /* Get the user to type in a direct command */
95 READRC, /* Read-in a named rc file */
96 QUIT, /* Quit */
97 HELP, /* Help */
98 FLUSH, /* Flush file buffer and friends */
99 REPAINT, /* Redraw the screen (useful if it got trashed) */
100 FORW_SCROLL, /* Scroll forward N lines */
101 BACK_SCROLL, /* Scroll backward N lines */
102 FORW, /* Jump or scroll forward N lines */
103 BACK, /* Jump or scroll backwards N lines */
104 LSCROLL, /* Scroll horizontally leftwards */
105 RSCROLL, /* Scroll horizontally to the right */
106 GOLINE, /* Goto line number N */
107 GOPERCENT, /* Goto percent N of the file */
108 GOEND, /* Goto the end of the file */
109 EDIT, /* Edit the current file, using getenv(EDITOR) */
110 ASKFILE, /* Ask for a different, new file */
111 CFILE, /* Page/view the N'th next or prev file */
112 FILE_LIST, /* List the files that CFILE moves around in */
113 STAT, /* List detailed file statistics in prompt */
114 MAGICASKSEARCH, /* Ask for a regexp search string */
115 SEARCH, /* Search for a regexp */
116 RESEARCH, /* Search for the next N'th occurrence */
117 SETMARK, /* Set a bookmark to the current position */
118 GOMARK, /* Goto a previously set bookmark */
119 LISTBMARKS, /* List the bookmark hints */
120 ASKFTAG, /* Ask for a tag to goto */
121 NEXTFTAG, /* Move forward N in the tag queue */
122 PREVFTAG, /* Move backwards N in the tag queue */
123 };
124
125 static struct ctable {
126 const char *cname;
127 enum cident cident;
128 const char * (*cfunc)(enum cident, const char *args);
129 } ctable[] = {
130 { "deftog", DEFTOG, cdeftog },
131 { "eval", EVAL, ceval },
132 { "set", SET, cset },
133 { "macro", MACRO, cmacro },
134 { "error", ERROR, cerror },
135 { "condition", CONDITION, ccondition },
136 { "condition_!", CONDITION_N, ccondition },
137 { "condition_toggle", CONDITION_TOGGLE, ccondition },
138 { "condition_else", CONDITION_TOGGLE, ccondition },
139 { "usercom", USERCOM, cusercom },
140 { "readrc", READRC, creadrc },
141 { "quit", QUIT, cquit },
142 { "help", HELP, chelp },
143 { "flush", FLUSH, cflush },
144 { "repaint", REPAINT, cflush },
145 { "forw_scroll", FORW_SCROLL, cscroll },
146 { "back_scroll", BACK_SCROLL, cscroll },
147 { "forw", FORW, cscroll },
148 { "back", BACK, cscroll },
149 { "rscroll", RSCROLL, chscroll },
150 { "lscroll", LSCROLL, chscroll },
151 { "goline", GOLINE, cgomisc },
152 { "gopercent", GOPERCENT, cgomisc },
153 { "goend", GOEND, cgoend },
154 { "edit", EDIT, cedit },
155 { "askfile", ASKFILE, caskfile },
156 { "file", CFILE, cfile },
157 { "file_list", FILE_LIST, cfile_list },
158 { "stat", STAT, cstat },
159 { "magicasksearch", MAGICASKSEARCH, csearch },
160 { "search", SEARCH, csearch },
161 { "research", RESEARCH, csearch },
162 { "setmark", SETMARK, cmark },
163 { "gomark", GOMARK, cmark },
164 { "listbmarks", LISTBMARKS, clistbmarks },
165 { "asktag", ASKFTAG, ctags },
166 { "nexttag", NEXTFTAG, ctags },
167 { "prevtag", PREVFTAG, ctags },
168 };
169
170
171 /* I believe this is just for cosmetic purposes. */
172 #define CMD_EXEC lower_left(); flush()
173
174
175 /*
176 * Prototypes are for people who can't program.
177 */
178
179
180 /*
181 * The main command string evaluator. Returns -1 if an error occurred
182 * in the command or in executing the command, returns 0 otherwise. If an
183 * error occurs while evaluating a command line containing multiple commands,
184 * commands after the error are not processed. Multiple commands may be
185 * separated by ';' or '\n'. (Multiple commands may also be separated by
186 * a ' ', but this is really a bug...)
187 */
188 int
command(line)189 command(line)
190 const char *line;
191 {
192 struct ctable *i;
193
194 donextcommand:
195
196 while (isspace(*line) || *line == ';' || *line == '\n') line++;
197 if (!*line)
198 return 0;
199
200 for (i = ctable; i != ctable + sizeof(ctable) / sizeof(struct ctable);
201 i++) {
202 if (!strncmp(i->cname, line, strlen(i->cname)) &&
203 (line[strlen(i->cname)] == ' ' ||
204 line[strlen(i->cname)] == ';' ||
205 line[strlen(i->cname)] == '\0')) {
206 /* Found a match! */
207 void **ctxt;
208 CMD_EXEC;
209 ctxt = getstr_raisectxt();
210 line = i->cfunc (i->cident, line + strlen(i->cname));
211 getstr_free(ctxt);
212 if (!line)
213 return -1; /* error evaluating command */
214 goto donextcommand;
215 }
216 }
217
218 SETERRSTR(E_BOGCOM, "invalid command: ``%s''", line);
219 (void) command("condition true");
220 return -1;
221 }
222
223
224 /*****************************************************************************
225 *
226 * Functions to help specific command functions to parse their arguments.
227 *
228 * The three functions here, getstr(), getint(), and gettog() could in theory
229 * have vastly different concepts of what a number is, and what a string is,
230 * etc., but in practice they don't.
231 */
232
233 static char *readvar();
234 static const char *getvar(char *, int);
235
236 #define NCTXTS 128
237 void *getstr_ctxts[NCTXTS]; /* could easily be made dynamic... */
238 void **getstr_curctxt = getstr_ctxts;
239
240 /*
241 * Read a single argument string from a command string. This understands
242 * $variables, "double quotes", 'single quotes', and backslash escapes
243 * for \\, \$, \n, \e, \t, and \". A string may be delimited by double
244 * quotes or spaces, not both (duh). It may be worthwhile to add
245 * another quotation style in which arithmetic expressions are expanded.
246 * Currently an arithmetic expression is expanded iff it is the only
247 * component of the string.
248 *
249 * Returns a pointer to the beginning of the string or NULL if it was unable to
250 * read a string. The line is modified to point somewhere between the end of
251 * the command argument just read-in and the beginning of the next command
252 * argument (if any). The returned pointer will be free()'d by calling
253 * getstr_free().
254 */
255 static char *
getstr(line)256 getstr(line)
257 const char **line; /* Where to look for the return string */
258 {
259 int doquotes = 0; /* Doing a double-quote string */
260 char *retr;
261
262 if (getstr_curctxt - getstr_ctxts == NCTXTS) {
263 SETERRSTR(E_COMPLIM,
264 "compile-time limit exceeded: command contexts");
265 return NULL; /* wouldn't be able to register return pointer */
266 }
267
268 while (isspace(**line)) (*line)++;
269 if (**line == '\'') {
270 /* Read until closing quote or '\0'. */
271 char *nextw = retr = malloc(1);
272 const char *c = ++(*line);
273 int l;
274 for (; *c; c++) {
275 if (*c == '\'') {
276 if (c[-1] == '\\') {
277 nextw[-1] = '\'';
278 continue;
279 } else {
280 *nextw = '\0';
281 *line = c + 1;
282 *getstr_curctxt = retr;
283 getstr_curctxt++;
284 return retr;
285 }
286 }
287 l = nextw - retr;
288 /* XXX How many realloc()'s can you make per second? */
289 if (!(retr = reallocf(retr, c - *line + 250))) {
290 SETERR (E_MALLOC);
291 return NULL;
292 }
293 nextw = retr + l;
294 *nextw = *c;
295 nextw++;
296 }
297 SETERR(E_CANTPARSE);
298 return NULL;
299 }
300 if (**line == '"') {
301 doquotes = 1;
302 (*line)++;
303 }
304 if (**line == '(') {
305 /* An arithmetic expression instead of a string... Well, I
306 * guess this is valid. See comment leading this function. */
307 long n;
308 if (getint(&n, line))
309 return NULL;
310 retr = NULL;
311 asprintf(&retr, "%ld", n);
312 if (!retr)
313 SETERR (E_MALLOC);
314 *getstr_curctxt = retr;
315 getstr_curctxt++;
316 return retr;
317 }
318
319 if (!FMALLOC(1, retr))
320 return NULL;
321 *retr = '\0';
322 for (;;) {
323 char *c, hack[2];
324
325 switch (**line) {
326 case '\\':
327 switch (*(*line + 1)) {
328 case '\\': case '$': case '\'':
329 case ' ': case ';':
330 hack[0] = *(*line + 1);
331 hack[1] = '\0';
332 c = hack;
333 (*line) += 2;
334 break;
335 case 't':
336 c = "\t";
337 (*line) += 2;
338 break;
339 case 'n':
340 c = "\n";
341 (*line) += 2;
342 break;
343 case 'e':
344 c = "\e";
345 (*line) += 2;
346 break;
347 case '"':
348 if (doquotes) {
349 c = "\"";
350 (*line) += 2;
351 break;
352 } else
353 ; /* fallthrough */
354 default:
355 c = "\\";
356 (*line)++;
357 break;
358 }
359 break;
360 case '$':
361 (*line)++;
362 if (!(c = readvar(line))) {
363 free (retr);
364 return NULL;
365 }
366 break;
367 case ' ': case '\t': case ';':
368 if (!doquotes) {
369 doquotes = 1;
370 case '"':
371 if (doquotes) {
372 /* The end of the string */
373 (*line)++;
374 case '\0':
375 *getstr_curctxt = retr;
376 getstr_curctxt++;
377 return retr;
378 }
379 }
380 /* fallthrough */
381 default:
382 hack[0] = **line;
383 hack[1] = '\0';
384 c = hack;
385 (*line)++;
386 break;
387 }
388
389 retr = reallocf(retr, strlen(retr) + strlen(c) + 1);
390 if (!retr) {
391 SETERR (E_MALLOC);
392 return NULL;
393 }
394 strcat(retr, c);
395 }
396 }
397
398 /*
399 * Returns a new context that should be passed to getstr_free() so that
400 * getstr_free() only free()'s memory from that particular context.
401 */
402 static void **
getstr_raisectxt()403 getstr_raisectxt()
404 {
405 return getstr_curctxt;
406 }
407
408 /*
409 * Calls free() on all memory from context or higher.
410 */
411 static
getstr_free(context)412 getstr_free(context)
413 void **context;
414 {
415 while (getstr_curctxt != context) {
416 getstr_curctxt--;
417 free (*getstr_curctxt);
418 }
419 }
420
421 /*
422 * Reads an integer value from a command string. Typed numbers must be
423 * in base10. If a '(' is found as the first character of the integer value,
424 * then getint() will read until a closing ')' unless interupted by an
425 * end-of-command marker (error). The parentheses are expected to contain a
426 * simple arithmetic statement involving only one '*', '/', etc. operation. The
427 * rightmost digit or the closing parenthesis should be followed by either a
428 * space or an end-of-command marker.
429 *
430 * Returns 0 on success, -1 on failure. The line will be modified to just
431 * after the last piece of text parsed.
432 *
433 * XXX We may add support for negative numbers, someday...
434 */
435 static int
getint(numb,line)436 getint(numb, line)
437 long *numb; /* The read-in number is returned through this */
438 const char **line; /* The command line from which to read numb */
439 {
440 long n;
441 int j;
442 char *p;
443 const char *t;
444
445 while (isspace(**line)) (*line)++;
446
447 switch (**line) {
448 case '(':
449 (*line)++;
450 if (getint(numb, line))
451 return -1;
452 while (isspace(**line)) (*line)++;
453 j = **line;
454 (*line)++;
455 if (j == ')')
456 return 0;
457 if (**line == '=' && (j == '!' || j == '=')
458 || j == '&' && **line == '&' || j == '|' && **line == '|')
459 j = (j << 8) + *((*line)++);
460 if (getint(&n, line))
461 return -1;
462 while (isspace(**line)) (*line)++;
463 if (**line != ')') {
464 SETERRSTR (E_BADMATH,
465 "missing arithmetic close parenthesis");
466 return -1;
467 } else
468 (*line)++;
469 switch (j) {
470 case ('!' << 8) + '=':
471 *numb = *numb != n;
472 return 0;
473 case ('=' << 8) + '=':
474 *numb = *numb == n;
475 return 0;
476 case ('&' << 8) + '&':
477 *numb = *numb && n;
478 return 0;
479 case ('|' << 8) + '|':
480 *numb = *numb || n;
481 return 0;
482 case '+':
483 *numb += n;
484 return 0;
485 case '-':
486 *numb -= n;
487 return 0;
488 case '*':
489 *numb *= n;
490 return 0;
491 case '/':
492 if (n == 0)
493 *numb = 1;
494 else
495 *numb /= n;
496 return 0;
497 default:
498 SETERRSTR (E_BADMATH,
499 "bad arithmetic operator: ``%c''", j);
500 return -1;
501 }
502 case '$':
503 t = (*line)++;
504 if (!(p = readvar(line)))
505 return -1;
506 if (!isdigit(*p)) {
507 SETERRSTR (E_BADMATH,
508 "non-number found (``%s'') "
509 "after expanding variable at ``%s''", p, t);
510 return -1;
511 }
512 *numb = atol(p);
513 return 0;
514 case '9': case '0': case '8': case '1': case '7': case '2': case '6':
515 case '3': case '5': case '4':
516 *numb = atol(*line);
517 while (isdigit(**line)) (*line)++;
518 return 0;
519 case '"': case '\'':
520 /* Uh-oh. It's really a string. We'll go through getstr()
521 * and hope for the best, but this isn't looking good. */
522 if (!(p = getstr(line)))
523 return -1;
524 *numb = atol(p);
525 return 0;
526 default:
527 SETERRSTR (E_BADMATH,
528 "non-number found, number expected, before parsing ``%s''",
529 *line);
530 return -1;
531 }
532 }
533
534 /*
535 * Read an argument from the command string and match that argument against
536 * a series of legitimate values. For example,
537 *
538 * command <<opt0|opt1|opt2>>
539 *
540 * This command by be given to the command() processor as a variant of either
541 * "command opt1" or "command 4", both of which will cause this function to
542 * return the value 1. This function returns -1 on failure.
543 *
544 * Note that an option (eg. "opt1") must _not_ start with a digit!!
545 */
546 static int
gettog(const char ** line,int nopts,...)547 gettog(const char **line, int nopts, ...)
548 {
549 char *str;
550 int n;
551 va_list opts;
552
553 if (!(str = getstr(line)))
554 return -1;
555
556 if (isdigit(*str)) {
557 n = atol(str) % nopts;
558 return n;
559 }
560
561 va_start(opts, nopts);
562 for (n=0; n < nopts; n++) {
563 if (!strcasecmp(str, va_arg(opts, const char *))) {
564 va_end(opts);
565 return n;
566 }
567 }
568 va_end(opts);
569 SETERR (E_NOTOG); /* XXX would be nice to list valid toggles... */
570 return -1;
571 }
572
573 /*
574 * A companion function for gettog(). Example,
575 *
576 * optnumb = gettog(&args, 3, "opt1", "opt2", "opt3");
577 * settog("_lastoptnumb", optnumb, "opt1", "opt2", "opt3");
578 *
579 * And the variable named _lastoptnumb_s will be set to one of "opt1", "opt2",
580 * or "opt3" as per the value of optnumb. The variable _lastoptnumb_n will
581 * also be set to a corresponding value. The optnumb argument had better
582 * be within the correct range (between 0 and 2 in the above example)!!
583 */
584 void
settog(const char * varname,int optval,int nargs,...)585 settog(const char *varname, int optval, int nargs, ...)
586 {
587 va_list opts;
588 char *s;
589 int optval_orig = optval;
590
591 assert(optval < nargs); assert(optval >= 0);
592 if (!(s = malloc(strlen(varname) + 3)))
593 return;
594 strcpy (s, varname);
595 va_start(opts, nargs);
596 for (; optval; optval--) va_arg(opts, const char *);
597 s[strlen(varname)] = '_';
598 s[strlen(varname) + 1] = 's';
599 s[strlen(varname) + 2] = '\0';
600 (void) setvar(s, va_arg(opts, const char *));
601 s[strlen(varname) + 1] = 'n';
602 (void) setvari(s, (long) optval_orig);
603 clear_error();
604 va_end(opts);
605 free(s);
606 }
607
608 /*
609 * Read {text} and return the string associated with the variable named
610 * <<text>>. Returns NULL on failure.
611 */
612 static char *
readvar(line)613 readvar(line)
614 char **line;
615 {
616 int vlength;
617 char *vstart;
618
619 if (**line != '{') {
620 SETERR (E_BADVAR);
621 return NULL;
622 }
623 (*line)++;
624 for (vlength = 0, vstart = *line; **line &&
625 (isprint(**line) && **line != '{' && **line != '}'); (*line)++)
626 vlength++;
627 if (**line != '}' || vlength == 0) {
628 SETERRSTR (E_BADVAR,
629 "bad character ``%c'' in variable ``%.*s''", **line,
630 vlength, vstart);
631 return NULL;
632 }
633 (*line)++;
634 return getvar(vstart, vlength);
635 }
636
637
638 /*****************************************************************************
639 *
640 * Track variables.
641 *
642 */
643
644 static struct vble {
645 struct vble *next;
646 char *name;
647 char *value;
648 } *vble_l; /* linked-list of existing variables */
649
650 /*
651 * Return a pointer to the string that variable var represents. Returns
652 * NULL if a match could not be found and sets erreur. The returned
653 * pointer is valid until any calls to setvar() or until a call to
654 * getstr_free() frees the current context.
655 */
656 static const char *
getvar(var,len)657 getvar(var, len)
658 char *var;
659 int len; /* strncmp(var, varmatch, len); is used to match variables */
660 {
661 struct vble *i;
662 char tcap[3];
663 char *s, *retr;
664 char *gettermcap();
665
666 if (sscanf (var, "_termcap_%2s", tcap) == 1 &&
667 len == sizeof "_termcap_XX" - 1) {
668 /* Magic termcap variable */
669 if ((s = gettermcap(tcap))) {
670 if (getstr_curctxt - getstr_ctxts == NCTXTS) {
671 SETERRSTR(E_COMPLIM,
672 "compile-time limit exceeded: "
673 "command contexts");
674 return NULL;
675 }
676 if (!FMALLOC(strlen(s) + 1, retr))
677 return NULL;
678 strcpy (retr, s);
679 *getstr_curctxt = retr;
680 getstr_curctxt++;
681 return retr;
682 } else {
683 return "";
684 }
685 }
686
687 for (i = vble_l; i; i = i->next) {
688 if (!strncasecmp (i->name, var, len))
689 return i->value;
690 }
691
692 SETERRSTR (E_BADVAR, "variable ``%.*s'' not set", len, var);
693 return NULL;
694 }
695
696 /*
697 * Set variable var to val. Both var and val may be destroyed by the
698 * caller after setvar().
699 *
700 * Returns -1 on failure, 0 on success.
701 */
702 int
setvar(var,val)703 setvar(var, val)
704 char *var; /* variable to set */
705 char *val; /* value to set variable to */
706 {
707 struct vble *i, *last;
708 char *var_n, *val_n;
709 char *c;
710
711 for (c = var; *c && (isprint(*c) && *c != '{' && *c != '}'); c++) ;
712 if (*c) {
713 SETERRSTR (E_BADVAR,
714 "bad character ``%c'' in variable ``%s''", *c, var);
715 return -1;
716 }
717
718 for (i = vble_l; i; last = i, i = i->next) {
719 if (!strcasecmp (i->name, var)) {
720 if (!FMALLOC(strlen(val) + 1, val_n))
721 return -1;
722 free(i->value);
723 i->value = val_n;
724 strcpy(i->value, val);
725 return 0;
726 }
727 }
728
729 /* Need to add another variable to the list vble_l */
730 if (!FMALLOC(strlen(var) + 1, var_n))
731 return -1;
732 if (!FMALLOC(strlen(val) + 1, val_n)) {
733 free(var_n);
734 return -1;
735 }
736 if (!vble_l) {
737 if (!FMALLOC(sizeof(struct vble), vble_l)) {
738 free(var_n), free(val_n);
739 return -1;
740 }
741 i = vble_l;
742 } else {
743 if (!FMALLOC(sizeof(struct vble), last->next)) {
744 free(var_n), free(val_n);
745 return -1;
746 }
747 i = last->next;
748 }
749 i->next = NULL;
750 i->name = var_n; strcpy(i->name, var);
751 i->value = val_n; strcpy(i->value, val);
752 return 0;
753 }
754
755 /*
756 * Set or reset, as appropriate, variable var to val.
757 */
758 int
setvari(var,val)759 setvari(var, val)
760 const char *var;
761 long val;
762 {
763 char n[21]; /* XXX */
764 snprintf(n, sizeof(n), "%ld", val);
765 n[20] = '\0';
766 setvar(var, n);
767 }
768
769
770 /*****************************************************************************
771 *
772 * Specific command functions. These aren't actually individual functions,
773 * since using a gigantic switch statement is faster to type, but they
774 * pretend to be individual functions.
775 *
776 */
777
778 int condition_eval = 1; /* false if we just parse commands, but do nothing */
779
780 #define ARGSTR(v) do { \
781 if (!((v) = getstr(&args))) return NULL; \
782 } while (0)
783 #define ARGNUM(v) do { \
784 if (getint(&(v), &args)) return NULL; \
785 } while (0)
786 /* semi-gratuitous use of GNU cpp extension */
787 #define ARGTOG(v, n, togs...) do { \
788 if (((v) = gettog(&args, n, togs)) == -1) \
789 return NULL; \
790 } while (0)
791 #define ENDPARSE do { \
792 if (!condition_eval) return args; \
793 } while (0)
794
795 /*
796 * deftog
797 *
798 * Set all toggle options to their default values, provided the toggle option
799 * is registered with this function. This command is meant to be used at the
800 * beginning of the startup command list.
801 */
802 static const char *
cdeftog(cident,args)803 cdeftog(cident, args)
804 enum cident cident;
805 const char *args;
806 {
807 extern int horiz_off, wraplines;
808
809 ENDPARSE;
810 settog("_statprompt", 1, 2, "on", "off");
811 settog("_ls_direction", 0, 2, "forw", "back");
812 settog("_ls_sense", 0, 2, "noinvert", "invert");
813 setvari("_curhscroll", (long) horiz_off);
814 settog("_wraplines", wraplines, 2, "off", "on");
815 setvar("_ls_regexp", "");
816 /*
817 * not present: _file_direction
818 */
819 return args;
820 }
821
822 /*
823 * eval <<string>>
824 *
825 * Passes string back into the command evaluator.
826 */
827 static const char *
ceval(cident,args)828 ceval(cident, args)
829 enum cident cident;
830 const char *args;
831 {
832 const char *com;
833
834 ARGSTR(com); /* The command line to evaluate */
835 ENDPARSE;
836
837 /* It's not clear what to do with the command() return code */
838 (void) command(com);
839
840 return args;
841 }
842
843 /*
844 * set <<variablename>> <<variablestring>>
845 *
846 * Sets variable variablename to string variablestring.
847 */
848 static const char *
cset(cident,args)849 cset(cident, args)
850 enum cident cident;
851 const char *args;
852 {
853 const char *str, *var;
854
855 ARGSTR(var); /* name of variable to set */
856 ARGSTR(str); /* value to set variable to */
857 ENDPARSE;
858
859 if (*var == '_') {
860 SETERRSTR (E_BADVAR,
861 "variables beginning with '_' are reserved");
862 return NULL;
863 }
864 if (setvar(var, str))
865 return NULL;
866
867 return args;
868 }
869
870 /*
871 * macro <<default_number>> <<keys>> <<command>>
872 *
873 * Associates the macro keys with command.
874 */
875 static const char *
cmacro(cident,args)876 cmacro(cident, args)
877 enum cident cident;
878 const char *args;
879 {
880 const char *keys, *com;
881 long num;
882
883 ARGNUM(num); /* the default number N for this macro */
884 ARGSTR(keys); /* string of keys representing a macro */
885 ARGSTR(com); /* command line to associate with macro */
886 ENDPARSE;
887
888 if (setmacro(keys, com))
889 return NULL;
890 if (setmacnumb(keys, num))
891 return NULL;
892
893 return args;
894 }
895
896 /*
897 * error <<string>>
898 *
899 * Prints a notification message.
900 */
901 static const char *
cerror(cident,args)902 cerror(cident, args)
903 enum cident cident;
904 const char *args;
905 {
906 char *s;
907
908 ARGSTR(s); /* error message */
909 ENDPARSE;
910 error(s);
911 return args;
912 }
913
914 /*
915 * condition <<boolean>>
916 * condition_! <<boolean>>
917 *
918 * If boolean is false, causes all commands except for other condition
919 * commands to be ignored. The <<boolean>> may be specified as a number
920 * (in which case even numbers are true, odd numbers are false), or one
921 * of "on", "off", "true", and "false".
922 */
923 static const char *
ccondition(cident,args)924 ccondition(cident, args)
925 enum cident cident;
926 const char *args;
927 {
928 /* ENDPARSE; */
929
930 if (cident == CONDITION_TOGGLE) {
931 condition_eval = !condition_eval;
932 return args;
933 }
934
935 switch (gettog(&args, 4, "off", "on", "false", "true")) {
936 case 0: case 2:
937 condition_eval = 0;
938 break;
939 case 1: case 3:
940 condition_eval = 1;
941 break;
942 case -1:
943 return NULL;
944 }
945 if (cident == CONDITION_N)
946 condition_eval = !condition_eval;
947 return args;
948 }
949
950 /*
951 * usercom
952 *
953 * Accept a direct command from the user's terminal.
954 */
955 static const char *
cusercom(cident,args)956 cusercom(cident, args)
957 enum cident cident;
958 const char *args;
959 {
960 char buf[125]; /* XXX should avoid static buffer... */
961
962 ENDPARSE;
963 if (getinput("Command: ", buf, sizeof(buf)))
964 return args; /* abort the command */
965 if (command(buf))
966 return NULL;
967
968 return args;
969 }
970
971 /*
972 * readrc <<filename>>
973 *
974 * Read-in rc commands from the named file.
975 */
976 static const char *
creadrc(cident,args)977 creadrc(cident, args)
978 enum cident cident;
979 const char *args;
980 {
981 const char *file;
982 FILE *fd;
983
984 ARGSTR(file);
985 ENDPARSE;
986
987 if (!*file)
988 return args;
989 /*
990 * Should perhaps warn user if file perms or ownership look suspicious.
991 */
992 fd = fopen(file, "r");
993 if (!fd) {
994 SETERRSTR (E_NULL, "could not open file ``%s''", file);
995 return NULL;
996 }
997 readrc(fd);
998 fclose(fd);
999
1000 return args;
1001 }
1002
1003 /*
1004 * quit
1005 *
1006 * Performs as advertised.
1007 */
1008 static const char *
cquit(cident,args)1009 cquit(cident, args)
1010 enum cident cident;
1011 const char *args;
1012 {
1013 ENDPARSE;
1014 quit();
1015 return NULL; /* oh boy... */
1016 }
1017
1018 /*
1019 * help
1020 *
1021 * Doesn't do much.
1022 */
1023 static const char *
chelp(cident,args)1024 chelp(cident, args)
1025 enum cident cident;
1026 const char *args;
1027 {
1028 extern int ac, curr_ac;
1029 extern char **av;
1030
1031 ENDPARSE;
1032 if (ac > 0 && !strcmp(_PATH_HELPFILE, av[curr_ac])) {
1033 SETERRSTR(E_NULL, "already viewing help");
1034 return NULL;
1035 }
1036 help();
1037 return args;
1038 }
1039
1040 /*
1041 * flush
1042 * repaint
1043 *
1044 * Flushes the file buffer, provided we are not reading from a pipe.
1045 * Frees any other memory that I can get my hands on from here.
1046 */
1047 static const char *
cflush(cident,args)1048 cflush(cident, args)
1049 enum cident cident;
1050 const char *args;
1051 {
1052 extern int ispipe;
1053
1054 ENDPARSE;
1055 if (cident == FLUSH && !ispipe) {
1056 ch_init(0, 0); /* XXX should this be ch_init(ctags,0) */
1057 clr_linenum();
1058 }
1059 repaint();
1060
1061 return args;
1062 }
1063
1064 /*
1065 * forw_scroll <<n>>
1066 * back_scroll <<n>>
1067 * forw <<n>>
1068 * back <<n>>
1069 *
1070 * Move forward number n lines. The _scroll variants force a scroll, the
1071 * others may scroll or may just redraw the screen at the appropriate location,
1072 * whichever is faster.
1073 */
1074 static const char *
cscroll(cident,args)1075 cscroll(cident, args)
1076 enum cident cident;
1077 const char *args;
1078 {
1079 long n;
1080 char *retr;
1081
1082 ARGNUM(n); /* number of lines to move by */
1083 ENDPARSE;
1084
1085 switch (cident) {
1086 case FORW_SCROLL:
1087 forward(n, 0);
1088 break;
1089 case BACK_SCROLL:
1090 backward(n, 0);
1091 break;
1092 case FORW:
1093 forward(n, 1);
1094 break;
1095 case BACK:
1096 backward(n, 1);
1097 break;
1098 }
1099
1100 return args;
1101 }
1102
1103 /*
1104 * rscroll <<n>>
1105 * lscroll <<n>>
1106 *
1107 * Scroll left or right by n lines.
1108 */
1109 static const char *
chscroll(cident,args)1110 chscroll(cident, args)
1111 enum cident cident;
1112 const char *args;
1113 {
1114 long n;
1115 char *retr;
1116 extern int horiz_off, wraplines;
1117
1118 ARGNUM(n); /* Number of columns to scroll by */
1119 ENDPARSE;
1120
1121 if (n == 0)
1122 return args;
1123
1124 switch (cident) {
1125 case RSCROLL:
1126 if (wraplines) {
1127 wraplines = 0;
1128 assert (horiz_off == 0);
1129 } else {
1130 horiz_off += n;
1131 if (horiz_off < 0)
1132 horiz_off = INT_MAX; /* disaster control */
1133 }
1134 break;
1135 case LSCROLL:
1136 if (horiz_off != 0) {
1137 horiz_off -= n;
1138 if (horiz_off < 0)
1139 horiz_off = 0;
1140 } else
1141 wraplines = 1;
1142 break;
1143 }
1144 repaint(); /* screen_trashed = 1 */
1145 setvari("_curhscroll", (long) horiz_off);
1146 settog("_wraplines", wraplines, 2, "off", "on");
1147
1148 return args;
1149 }
1150
1151 /*
1152 * goline <<line>>
1153 * gopercent <<percent>>
1154 *
1155 * Goto the line numbered <<line>>, if possible. Goto <<percent>> percent of
1156 * the file. Whole-numbered percents only, of course.
1157 */
1158 static const char *
cgomisc(cident,args)1159 cgomisc(cident, args)
1160 enum cident cident;
1161 const char *args;
1162 {
1163 long n;
1164
1165 ARGNUM(n); /* number N */
1166 ENDPARSE;
1167
1168 switch (cident) {
1169 case GOLINE:
1170 jump_back(n);
1171 break;
1172 case GOPERCENT:
1173 if (n > 100)
1174 n = 100;
1175 jump_percent(n);
1176 break;
1177 }
1178 return args;
1179 }
1180
1181 /*
1182 * listbmarks
1183 *
1184 * Give some hints on where each of the bookmarks lead to.
1185 */
1186 static const char *
clistbmarks(cident,args)1187 clistbmarks(cident, args)
1188 enum cident cident;
1189 const char *args;
1190 {
1191 ENDPARSE;
1192
1193 if (marks_bookhints())
1194 return NULL;
1195 return args;
1196 }
1197
1198 /*
1199 * goend
1200 *
1201 * Goto the end of the file. Future variation should include the GNU less(1)-
1202 * style follow a-la tail(1).
1203 */
1204 static const char *
cgoend(cident,args)1205 cgoend(cident, args)
1206 enum cident cident;
1207 const char *args;
1208 {
1209 ENDPARSE;
1210 jump_forw();
1211 return args;
1212 }
1213
1214 /*
1215 * edit
1216 *
1217 * Edits the current file with a word editor. This command is just begging
1218 * to be extended to allow the user to specify an editor. Additionally, this
1219 * would require some kind of getenv command or similar change.
1220 */
1221 static const char *
cedit(cident,args)1222 cedit(cident, args)
1223 enum cident cident;
1224 const char *args;
1225 {
1226 extern ispipe;
1227
1228 ENDPARSE;
1229 if (ispipe) {
1230 SETERRSTR(E_NULL, "cannot edit standard input");
1231 return NULL;
1232 }
1233 editfile();
1234 /*
1235 * XXX less-than brilliant things happen if the user while editing
1236 * deletes a large section at the end of the file where we think we
1237 * are currently viewing...
1238 */
1239 ch_init(0, 0); /* Clear the internal file buffer */
1240 clr_linenum();
1241 return args;
1242 }
1243
1244 /*
1245 * askfile
1246 *
1247 * Loads a new file. Queries the user for the name of the new file.
1248 */
1249 static const char *
caskfile(cident,args)1250 caskfile(cident, args)
1251 enum cident cident;
1252 const char *args;
1253 {
1254 char buf[MAXPATHLEN + 1];
1255
1256 ENDPARSE;
1257 if (getinput("Examine: ", buf, sizeof(buf)))
1258 return args; /* abort */
1259 /* Abort is different from a "" answer in that "" causes the current
1260 * file to be re-opened. */
1261 /* XXX should modify this() or edit() to handle lists of file, ie.
1262 * the type of lists that I get if I try to glob("*") */
1263 (void)edit(glob(buf), FORCE_OPEN);
1264
1265 return args;
1266 }
1267
1268 /*
1269 * file <<next|previous>> <<N>>
1270 *
1271 * Loads the N'th next or previous file, typically from the list of files
1272 * given on the command line.
1273 */
1274 static const char *
cfile(cident,args)1275 cfile(cident, args)
1276 enum cident cident;
1277 const char *args;
1278 {
1279 enum { FORW=0, BACK=1 } direction;
1280 long N;
1281
1282 ARGTOG(direction, 10, "next", "previous", "forward", "backward",
1283 "forwards", "backwards", "next", "prev", "forw", "back");
1284 ARGNUM(N);
1285 ENDPARSE;
1286 direction %= 2;
1287
1288 /* next_file() and prev_file() call error() directly (bad) */
1289 switch (direction) {
1290 case FORW:
1291 next_file(N);
1292 break;
1293 case BACK:
1294 prev_file(N);
1295 break;
1296 }
1297 settog("_file_direction", direction, 2, "next", "previous");
1298 return args;
1299 }
1300
1301 /*
1302 * file_list
1303 *
1304 * Lists the files the "file next" and "file prev" are moving around in.
1305 */
1306 static const char *
cfile_list(cident,args)1307 cfile_list(cident, args)
1308 enum cident cident;
1309 const char *args;
1310 {
1311 ENDPARSE;
1312 showlist();
1313 repaint(); /* screen_trashed = 1; */
1314 return args;
1315 }
1316
1317 /*
1318 * stat <<on|off>>
1319 *
1320 * Display the detailed statistics as part of the prompt. The toggle option
1321 * variable is called _statprompt (giving ${_statprompt_s} and
1322 * ${_statprompt_n}).
1323 */
1324 static const char *
cstat(cident,args)1325 cstat(cident, args)
1326 enum cident cident;
1327 const char *args;
1328 {
1329 int onoff;
1330
1331 ARGTOG(onoff, 2, "on", "off");
1332 ENDPARSE;
1333 statprompt(onoff);
1334 settog("_statprompt", onoff, 2, "on", "off");
1335 return args;
1336 }
1337
1338 /*
1339 * magicasksearch <<forw|back>> <<n>>
1340 * search <<forw|back>> <<n>> <<<noinvert|invert>> <searchstring>>
1341 * research <<forw|back>> <<n>>
1342 *
1343 * Arguments specifying an option (ie. <<forw|back>> and <<noinvert|invert>>
1344 * may be specified either as text (eg. "forw"), or as a number, in which case
1345 * even numbers specify the former setting and odd numbers the latter setting.
1346 *
1347 * The magicasksearch will ask the user for a regexp and intuit whether they
1348 * want to invert the sense of matching or not: if the first character of the
1349 * regexp is a '!', it is removed and the sense is inverted. If the regexp
1350 * entered is null, then we will use ${_ls_regexp} (error if not set).
1351 *
1352 * The toggle options are called _ls_direction and _ls_sense. In addition,
1353 * ${_ls_regexp} is set to the regexp used. These variables are only set
1354 * when the search and magicsearch commands are used.
1355 */
1356 static const char *
csearch(cident,args)1357 csearch(cident, args)
1358 enum cident cident;
1359 const char *args;
1360 {
1361 char buf[100], *str;
1362 enum { FORW=0, BACK=1 } direction;
1363 static enum { NOINVERT=0, INVERT=1 } sense;
1364 long N;
1365 int abrt = 0;
1366
1367 ARGTOG(direction, 6, "forw", "back", "forward", "backward",
1368 "forwards", "backwards");
1369 ARGNUM(N);
1370 if (cident == SEARCH) {
1371 ARGTOG(sense, 2, "noinvert", "invert");
1372 ARGSTR(str);
1373 }
1374 ENDPARSE;
1375 direction %= 2;
1376
1377 /* Get the search string, one way or another */
1378 switch (cident) {
1379 case MAGICASKSEARCH:
1380 biggetinputhack(); /* It's magic, boys */
1381 if (direction == FORW)
1382 abrt = getinput("Search: /", buf, 2);
1383 else
1384 abrt = getinput("Search: ?", buf, 2);
1385 switch (*buf) {
1386 case '!':
1387 /* Magic */
1388 if (direction == FORW)
1389 abrt = getinput("Search: !/", buf, sizeof(buf));
1390 else
1391 abrt = getinput("Search: !?", buf, sizeof(buf));
1392 sense = INVERT;
1393 break;
1394 default:
1395 /* No magic */
1396 ungetcc(*buf);
1397 if (direction == FORW)
1398 abrt = getinput("Search: /", buf, sizeof(buf));
1399 else
1400 abrt = getinput("Search: ?", buf, sizeof(buf));
1401 case '\0':
1402 sense = NOINVERT;
1403 break;
1404 }
1405 str = buf;
1406 break;
1407 case SEARCH:
1408 break;
1409 case RESEARCH:
1410 str = NULL;
1411 break;
1412 }
1413
1414 if (abrt)
1415 return args;
1416
1417 if (cident == SEARCH || cident == MAGICASKSEARCH) {
1418 settog("_ls_direction", direction, 2, "forw", "back");
1419 settog("_ls_sense", sense, 2, "noinvert", "invert");
1420 if (*str)
1421 setvar("_ls_regexp", str);
1422 }
1423 /*
1424 * XXX Currently search() contains magic to deal with (*str=='\0').
1425 * This magic should be moved into this function so that we can work
1426 * as described in the function comment header.
1427 */
1428 search(!direction, str, N, !sense);
1429 return args;
1430 }
1431
1432 /*
1433 * setmark <<character>>
1434 * gomark <<character>>
1435 *
1436 * Set a marker at the current position, or goto a previously set marker.
1437 * Character may be a-z, or '?' to ask the user to enter a character. The
1438 * special mark '\'' may not be set, but may be the target of a goto.
1439 */
1440 static const char *
cmark(cident,args)1441 cmark(cident, args)
1442 enum cident cident;
1443 const char *args;
1444 {
1445 char smark[2];
1446 const char *mark;
1447
1448 ARGSTR(mark);
1449 ENDPARSE;
1450
1451 /* gomark() and setmark() will further check mark's validity */
1452 if (!*mark || mark[1]) {
1453 SETERRSTR(E_NULL, "bad mark character");
1454 return NULL;
1455 }
1456
1457 if (*mark == '?') {
1458 biggetinputhack(); /* so getinput() returns after one char */
1459 switch (cident) {
1460 case GOMARK:
1461 getinput("goto mark: ", smark, sizeof smark);
1462 break;
1463 case SETMARK:
1464 getinput("set mark: ", smark, sizeof smark);
1465 break;
1466 }
1467 if (!*smark)
1468 return args;
1469 mark = smark;
1470 }
1471
1472 switch (cident) {
1473 case GOMARK:
1474 gomark(*mark);
1475 break;
1476 case SETMARK:
1477 setmark(*mark);
1478 break;
1479 }
1480 return args;
1481 }
1482
1483 /*
1484 * asktag
1485 * nexttag <<number>>
1486 * prevtag <<number>>
1487 *
1488 * Asks the user for a tag, or moves around the tag queue.
1489 */
1490 static const char *
ctags(cident,args)1491 ctags(cident, args)
1492 enum cident cident;
1493 const char *args;
1494 {
1495 extern char *tagfile; /* XXX No reason for this to be a global... */
1496 long n;
1497
1498 if (cident != ASKFTAG)
1499 ARGNUM(n);
1500 ENDPARSE;
1501
1502 if (cident == ASKFTAG) {
1503 char buf[100]; /* XXX should do something else... */
1504 getinput("Tag: ", buf, sizeof(buf));
1505 if (!*buf)
1506 return args;
1507 findtag(buf);
1508 } else {
1509 switch (cident) {
1510 case NEXTFTAG:
1511 nexttag(n);
1512 break;
1513 case PREVFTAG:
1514 prevtag(n);
1515 break;
1516 }
1517 }
1518
1519 /* Load the tagfile and position ourselves. */
1520 if (tagfile == NULL)
1521 return NULL;
1522 if (edit(tagfile, NO_FORCE_OPEN))
1523 tagsearch();
1524 return args; /* tag stuff still calls error() on its own */
1525 }
1526