coverity appeasement
[minix.git] / commands / elvis / ex.c
blob064e662080c2c17f3ec29b9d5ea434d845f31761
1 /* ex.c */
3 /* Author:
4 * Steve Kirkendall
5 * 14407 SW Teal Blvd. #C
6 * Beaverton, OR 97005
7 * kirkenda@cs.pdx.edu
8 */
11 /* This file contains the code for reading ex commands. */
13 #include "config.h"
14 #include "ctype.h"
15 #include "vi.h"
17 /* This data type is used to describe the possible argument combinations */
18 typedef short ARGT;
19 #define FROM 1 /* allow a linespec */
20 #define TO 2 /* allow a second linespec */
21 #define BANG 4 /* allow a ! after the command name */
22 #define EXTRA 8 /* allow extra args after command name */
23 #define XFILE 16 /* expand wildcards in extra part */
24 #define NOSPC 32 /* no spaces allowed in the extra part */
25 #define DFLALL 64 /* default file range is 1,$ */
26 #define DFLNONE 128 /* no default file range */
27 #define NODFL 256 /* do not default to the current file name */
28 #define EXRCOK 512 /* can be in a .exrc file */
29 #define NL 1024 /* if mode!=MODE_EX, then write a newline first */
30 #define PLUS 2048 /* allow a line number, as in ":e +32 foo" */
31 #define ZERO 4096 /* allow 0 to be given as a line number */
32 #define NOBAR 8192 /* treat following '|' chars as normal */
33 #define FILES (XFILE + EXTRA) /* multiple extra files allowed */
34 #define WORD1 (EXTRA + NOSPC) /* one extra word allowed */
35 #define FILE1 (FILES + NOSPC) /* 1 file allowed, defaults to current file */
36 #define NAMEDF (FILE1 + NODFL) /* 1 file allowed, defaults to "" */
37 #define NAMEDFS (FILES + NODFL) /* multiple files allowed, default is "" */
38 #define RANGE (FROM + TO) /* range of linespecs allowed */
39 #define NONE 0 /* no args allowed at all */
41 /* This array maps ex command names to command codes. The order in which
42 * command names are listed below is significant -- ambiguous abbreviations
43 * are always resolved to be the first possible match. (e.g. "r" is taken
44 * to mean "read", not "rewind", because "read" comes before "rewind")
46 static struct
48 char *name; /* name of the command */
49 CMD code; /* enum code of the command */
50 void (*fn)();/* function which executes the command */
51 ARGT argt; /* command line arguments permitted/needed/used */
53 cmdnames[] =
54 { /* cmd name cmd code function arguments */
55 {"append", CMD_APPEND, cmd_append, FROM+ZERO+BANG },
56 #ifdef DEBUG
57 {"bug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA+NL},
58 #endif
59 {"change", CMD_CHANGE, cmd_append, RANGE+BANG },
60 {"delete", CMD_DELETE, cmd_delete, RANGE+WORD1 },
61 {"edit", CMD_EDIT, cmd_edit, BANG+FILE1+PLUS },
62 {"file", CMD_FILE, cmd_file, NAMEDF },
63 {"global", CMD_GLOBAL, cmd_global, RANGE+BANG+EXTRA+DFLALL+NOBAR},
64 {"insert", CMD_INSERT, cmd_append, FROM+BANG },
65 {"join", CMD_INSERT, cmd_join, RANGE+BANG },
66 {"k", CMD_MARK, cmd_mark, FROM+WORD1 },
67 {"list", CMD_LIST, cmd_print, RANGE+NL },
68 {"move", CMD_MOVE, cmd_move, RANGE+EXTRA },
69 {"next", CMD_NEXT, cmd_next, BANG+NAMEDFS },
70 {"Next", CMD_PREVIOUS, cmd_next, BANG },
71 {"print", CMD_PRINT, cmd_print, RANGE+NL },
72 {"quit", CMD_QUIT, cmd_xit, BANG },
73 {"read", CMD_READ, cmd_read, FROM+ZERO+NAMEDF},
74 {"substitute", CMD_SUBSTITUTE, cmd_substitute, RANGE+EXTRA },
75 {"to", CMD_COPY, cmd_move, RANGE+EXTRA },
76 {"undo", CMD_UNDO, cmd_undo, NONE },
77 {"vglobal", CMD_VGLOBAL, cmd_global, RANGE+EXTRA+DFLALL+NOBAR},
78 {"write", CMD_WRITE, cmd_write, RANGE+BANG+FILE1+DFLALL},
79 {"xit", CMD_XIT, cmd_xit, BANG+NL },
80 {"yank", CMD_YANK, cmd_delete, RANGE+WORD1 },
82 {"!", CMD_BANG, cmd_shell, EXRCOK+RANGE+NAMEDFS+DFLNONE+NL+NOBAR},
83 {"#", CMD_NUMBER, cmd_print, RANGE+NL },
84 {"<", CMD_SHIFTL, cmd_shift, RANGE },
85 {">", CMD_SHIFTR, cmd_shift, RANGE },
86 {"=", CMD_EQUAL, cmd_file, RANGE },
87 {"&", CMD_SUBAGAIN, cmd_substitute, RANGE },
88 #ifndef NO_AT
89 {"@", CMD_AT, cmd_at, EXTRA },
90 #endif
92 #ifndef NO_ABBR
93 {"abbreviate", CMD_ABBR, cmd_map, EXRCOK+BANG+EXTRA},
94 #endif
95 {"args", CMD_ARGS, cmd_args, EXRCOK+NAMEDFS },
96 #ifndef NO_ERRLIST
97 {"cc", CMD_CC, cmd_make, BANG+FILES },
98 #endif
99 {"cd", CMD_CD, cmd_cd, EXRCOK+BANG+NAMEDF},
100 {"copy", CMD_COPY, cmd_move, RANGE+EXTRA },
101 #ifndef NO_DIGRAPH
102 {"digraph", CMD_DIGRAPH, cmd_digraph, EXRCOK+BANG+EXTRA},
103 #endif
104 #ifndef NO_ERRLIST
105 {"errlist", CMD_ERRLIST, cmd_errlist, BANG+NAMEDF },
106 #endif
107 {"ex", CMD_EDIT, cmd_edit, BANG+FILE1 },
108 {"mark", CMD_MARK, cmd_mark, FROM+WORD1 },
109 #ifndef NO_MKEXRC
110 {"mkexrc", CMD_MKEXRC, cmd_mkexrc, NAMEDF },
111 #endif
112 {"number", CMD_NUMBER, cmd_print, RANGE+NL },
113 {"put", CMD_PUT, cmd_put, FROM+ZERO+WORD1 },
114 {"set", CMD_SET, cmd_set, EXRCOK+EXTRA },
115 {"shell", CMD_SHELL, cmd_shell, NL },
116 {"source", CMD_SOURCE, cmd_source, EXRCOK+NAMEDF },
117 #ifdef SIGTSTP
118 {"stop", CMD_STOP, cmd_suspend, NONE },
119 #endif
120 {"tag", CMD_TAG, cmd_tag, BANG+WORD1 },
121 {"version", CMD_VERSION, cmd_version, EXRCOK+NONE },
122 {"visual", CMD_VISUAL, cmd_edit, BANG+NAMEDF },
123 {"wq", CMD_WQUIT, cmd_xit, NL },
125 #ifdef DEBUG
126 {"debug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA+NL},
127 {"validate", CMD_VALIDATE, cmd_validate, BANG+NL },
128 #endif
129 {"chdir", CMD_CD, cmd_cd, EXRCOK+BANG+NAMEDF},
130 #ifndef NO_COLOR
131 {"color", CMD_COLOR, cmd_color, EXRCOK+EXTRA },
132 #endif
133 #ifndef NO_ERRLIST
134 {"make", CMD_MAKE, cmd_make, BANG+NAMEDFS },
135 #endif
136 {"map", CMD_MAP, cmd_map, EXRCOK+BANG+EXTRA},
137 {"previous", CMD_PREVIOUS, cmd_next, BANG },
138 {"rewind", CMD_REWIND, cmd_next, BANG },
139 #ifdef SIGTSTP
140 {"suspend", CMD_SUSPEND, cmd_suspend, NONE },
141 #endif
142 {"unmap", CMD_UNMAP, cmd_map, EXRCOK+BANG+EXTRA},
143 #ifndef NO_ABBR
144 {"unabbreviate",CMD_UNABBR, cmd_map, EXRCOK+WORD1 },
145 #endif
147 {(char *)0}
151 /* This function parses a search pattern - given a pointer to a / or ?,
152 * it replaces the ending / or ? with a \0, and returns a pointer to the
153 * stuff that came after the pattern.
155 char *parseptrn(ptrn)
156 REG char *ptrn;
158 REG char *scan;
160 for (scan = ptrn + 1;
161 *scan && *scan != *ptrn;
162 scan++)
164 /* allow backslashed versions of / and ? in the pattern */
165 if (*scan == '\\' && scan[1] != '\0')
167 scan++;
170 if (*scan)
172 *scan++ = '\0';
175 return scan;
179 /* This function parses a line specifier for ex commands */
180 char *linespec(s, markptr)
181 REG char *s; /* start of the line specifier */
182 MARK *markptr; /* where to store the mark's value */
184 long num;
185 REG char *t;
187 /* parse each ;-delimited clause of this linespec */
190 /* skip an initial ';', if any */
191 if (*s == ';')
193 s++;
196 /* skip leading spaces */
197 while (isspace(*s))
199 s++;
202 /* dot means current position */
203 if (*s == '.')
205 s++;
206 *markptr = cursor;
208 /* '$' means the last line */
209 else if (*s == '$')
211 s++;
212 *markptr = MARK_LAST;
214 /* digit means an absolute line number */
215 else if (isdigit(*s))
217 for (num = 0; isdigit(*s); s++)
219 num = num * 10 + *s - '0';
221 *markptr = MARK_AT_LINE(num);
223 /* appostrophe means go to a set mark */
224 else if (*s == '\'')
226 s++;
227 *markptr = m_tomark(cursor, 1L, (int)*s);
228 s++;
230 /* slash means do a search */
231 else if (*s == '/' || *s == '?')
233 /* put a '\0' at the end of the search pattern */
234 t = parseptrn(s);
236 /* search for the pattern */
237 *markptr &= ~(BLKSIZE - 1);
238 if (*s == '/')
240 pfetch(markline(*markptr));
241 if (plen > 0)
242 *markptr += plen - 1;
243 *markptr = m_fsrch(*markptr, s);
245 else
247 *markptr = m_bsrch(*markptr, s);
250 /* adjust command string pointer */
251 s = t;
254 /* if linespec was faulty, quit now */
255 if (!*markptr)
257 return s;
260 /* maybe add an offset */
261 t = s;
262 if (*t == '-' || *t == '+')
264 s++;
265 for (num = 0; isdigit(*s); s++)
267 num = num * 10 + *s - '0';
269 if (num == 0)
271 num = 1;
273 *markptr = m_updnto(*markptr, num, *t);
275 } while (*s == ';' || *s == '+' || *s == '-');
277 /* protect against invalid line numbers */
278 num = markline(*markptr);
279 if (num < 1L || num > nlines)
281 msg("Invalid line number -- must be from 1 to %ld", nlines);
282 *markptr = MARK_UNSET;
285 return s;
290 /* This function reads an ex command and executes it. */
291 void ex()
293 char cmdbuf[150];
294 REG int cmdlen;
295 static long oldline;
297 significant = FALSE;
298 oldline = markline(cursor);
300 while (mode == MODE_EX)
302 /* read a line */
303 #ifdef CRUNCH
304 cmdlen = vgets(':', cmdbuf, sizeof(cmdbuf));
305 #else
306 cmdlen = vgets(*o_prompt ? ':' : '\0', cmdbuf, sizeof(cmdbuf));
307 #endif
308 if (cmdlen < 0)
310 return;
313 /* if empty line, assume ".+1" */
314 if (cmdlen == 0)
316 strcpy(cmdbuf, ".+1");
317 qaddch('\r');
318 clrtoeol();
320 else
322 addch('\n');
324 refresh();
326 /* parse & execute the command */
327 doexcmd(cmdbuf);
329 /* handle autoprint */
330 if (significant || markline(cursor) != oldline)
332 significant = FALSE;
333 oldline = markline(cursor);
334 if (*o_autoprint && mode == MODE_EX)
336 cmd_print(cursor, cursor, CMD_PRINT, FALSE, "");
342 void doexcmd(cmdbuf)
343 char *cmdbuf; /* string containing an ex command */
345 REG char *scan; /* used to scan thru cmdbuf */
346 MARK frommark; /* first linespec */
347 MARK tomark; /* second linespec */
348 REG int cmdlen; /* length of the command name given */
349 CMD cmd; /* what command is this? */
350 ARGT argt; /* argument types for this command */
351 short forceit; /* bang version of a command? */
352 REG int cmdidx; /* index of command */
353 REG char *build; /* used while copying filenames */
354 int iswild; /* boolean: filenames use wildcards? */
355 int isdfl; /* using default line ranges? */
356 int didsub; /* did we substitute file names for % or # */
358 /* ex commands can't be undone via the shift-U command */
359 U_line = 0L;
361 /* permit extra colons at the start of the line */
362 for (; *cmdbuf == ':'; cmdbuf++)
366 /* ignore command lines that start with a double-quote */
367 if (*cmdbuf == '"')
369 return;
371 scan = cmdbuf;
373 /* parse the line specifier */
374 if (nlines < 1)
376 /* no file, so don't allow addresses */
378 else if (*scan == '%')
380 /* '%' means all lines */
381 frommark = MARK_FIRST;
382 tomark = MARK_LAST;
383 scan++;
385 else if (*scan == '0')
387 frommark = tomark = MARK_UNSET;
388 scan++;
390 else
392 frommark = cursor;
393 scan = linespec(scan, &frommark);
394 tomark = frommark;
395 if (frommark && *scan == ',')
397 scan++;
398 scan = linespec(scan, &tomark);
400 if (!tomark)
402 /* faulty line spec -- fault already described */
403 return;
405 if (frommark > tomark)
407 msg("first address exceeds the second");
408 return;
411 isdfl = (scan == cmdbuf);
413 /* skip whitespace */
414 while (isspace(*scan))
416 scan++;
419 /* if no command, then just move the cursor to the mark */
420 if (!*scan)
422 if (tomark != MARK_UNSET)
423 cursor = tomark;
424 return;
427 /* figure out how long the command name is */
428 if (!isalpha(*scan))
430 cmdlen = 1;
432 else
434 for (cmdlen = 1;
435 isalpha(scan[cmdlen]);
436 cmdlen++)
441 /* lookup the command code */
442 for (cmdidx = 0;
443 cmdnames[cmdidx].name && strncmp(scan, cmdnames[cmdidx].name, cmdlen);
444 cmdidx++)
447 argt = cmdnames[cmdidx].argt;
448 cmd = cmdnames[cmdidx].code;
449 if (cmd == CMD_NULL)
451 msg("Unknown command \"%.*s\"", cmdlen, scan);
452 return;
455 /* !!! if the command doesn't have NOBAR set, then replace | with \0 */
457 /* if the command ended with a bang, set the forceit flag */
458 scan += cmdlen;
459 if ((argt & BANG) && *scan == '!')
461 scan++;
462 forceit = 1;
464 else
466 forceit = 0;
469 /* skip any more whitespace, to leave scan pointing to arguments */
470 while (isspace(*scan))
472 scan++;
475 /* a couple of special cases for filenames */
476 if (argt & XFILE)
478 /* if names were given, process them */
479 if (*scan)
481 for (build = tmpblk.c, iswild = didsub = FALSE; *scan; scan++)
483 switch (*scan)
485 case '\\':
486 if (scan[1] == '\\' || scan[1] == '%' || scan[1] == '#')
488 *build++ = *++scan;
490 else
492 *build++ = '\\';
494 break;
496 case '%':
497 if (!*origname)
499 msg("No filename to substitute for %%");
500 return;
502 strcpy(build, origname);
503 while (*build)
505 build++;
507 didsub = TRUE;
508 break;
510 case '#':
511 if (!*prevorig)
513 msg("No filename to substitute for #");
514 return;
516 strcpy(build, prevorig);
517 while (*build)
519 build++;
521 didsub = TRUE;
522 break;
524 case '*':
525 case '?':
526 #if !(MSDOS || TOS)
527 case '[':
528 case '`':
529 case '{': /* } */
530 case '$':
531 case '~':
532 #endif
533 *build++ = *scan;
534 iswild = TRUE;
535 break;
537 default:
538 *build++ = *scan;
541 *build = '\0';
543 if (cmd == CMD_BANG
544 || cmd == CMD_READ && tmpblk.c[0] == '!'
545 || cmd == CMD_WRITE && tmpblk.c[0] == '!')
547 if (didsub)
549 if (mode != MODE_EX)
551 addch('\n');
553 addstr(tmpblk.c);
554 addch('\n');
555 exrefresh();
558 else
560 if (iswild && tmpblk.c[0] != '>')
562 scan = wildcard(tmpblk.c);
566 else /* no names given, maybe assume origname */
568 if (!(argt & NODFL))
570 strcpy(tmpblk.c, origname);
572 else
574 *tmpblk.c = '\0';
578 scan = tmpblk.c;
581 /* bad arguments? */
582 if (!(argt & EXRCOK) && nlines < 1L)
584 msg("Can't use the \"%s\" command in a %s file", cmdnames[cmdidx].name, EXRC);
585 return;
587 if (!(argt & (ZERO | EXRCOK)) && frommark == MARK_UNSET)
589 msg("Can't use address 0 with \"%s\" command.", cmdnames[cmdidx].name);
590 return;
592 if (!(argt & FROM) && frommark != cursor && nlines >= 1L)
594 msg("Can't use address with \"%s\" command.", cmdnames[cmdidx].name);
595 return;
597 if (!(argt & TO) && tomark != frommark && nlines >= 1L)
599 msg("Can't use a range with \"%s\" command.", cmdnames[cmdidx].name);
600 return;
602 if (!(argt & EXTRA) && *scan)
604 msg("Extra characters after \"%s\" command.", cmdnames[cmdidx].name);
605 return;
607 if ((argt & NOSPC) && !(cmd == CMD_READ && (forceit || *scan == '!')))
609 build = scan;
610 #ifndef CRUNCH
611 if ((argt & PLUS) && *build == '+')
613 while (*build && !isspace(*build))
615 build++;
617 while (*build && isspace(*build))
619 build++;
622 #endif /* not CRUNCH */
623 for (; *build; build++)
625 if (isspace(*build))
627 msg("Too many %s to \"%s\" command.",
628 (argt & XFILE) ? "filenames" : "arguments",
629 cmdnames[cmdidx].name);
630 return;
635 /* some commands have special default ranges */
636 if (isdfl && (argt & DFLALL))
638 frommark = MARK_FIRST;
639 tomark = MARK_LAST;
641 else if (isdfl && (argt & DFLNONE))
643 frommark = tomark = 0L;
646 /* write a newline if called from visual mode */
647 if ((argt & NL) && mode != MODE_EX && !exwrote)
649 addch('\n');
650 exrefresh();
653 /* act on the command */
654 (*cmdnames[cmdidx].fn)(frommark, tomark, cmd, forceit, scan);
658 /* This function executes EX commands from a file. It returns 1 normally, or
659 * 0 if the file could not be opened for reading.
661 int doexrc(filename)
662 char *filename; /* name of a ".exrc" file */
664 int fd; /* file descriptor */
665 int len; /* length of the ".exrc" file */
667 /* !!! kludge: we use U_text as the buffer. This has the side-effect
668 * of interfering with the shift-U visual command. Disable shift-U.
670 U_line = 0L;
672 /* open the file, read it, and close */
673 fd = open(filename, O_RDONLY);
674 if (fd < 0)
676 return 0;
678 len = tread(fd, U_text, BLKSIZE);
679 close(fd);
681 /* execute the string */
682 exstring(U_text, len, ctrl('V'));
684 return 1;
687 /* This function executes EX commands from a string. The commands may be
688 * separated by newlines or by | characters. It also handles quoting.
689 * Each individual command is limited to 132 bytes, but the total string
690 * may be longer.
692 void exstring(buf, len, qchar)
693 char *buf; /* the commands to execute */
694 int len; /* the length of the string */
695 int qchar; /* the quote character -- ^V for file, or \ for kbd */
697 char single[133]; /* a single command */
698 char *src, *dest;
699 int i;
701 /* find & do each command */
702 for (src = buf; src < &buf[len]; src++)
704 /* Copy a single command into single[]. Convert any quoted |
705 * into a normal |, and stop at a newline or unquoted |.
707 for (dest = single, i = 0;
708 i < 132 && src < &buf[len] && *src != '\n' && *src != '|';
709 src++, i++)
711 if (src[0] == qchar && src[1] == '|')
713 src++;
715 *dest++ = *src;
717 *dest = '\0';
719 /* do it */
720 doexcmd(single);