This commit was generated by cvs2svn to track changes on a CVS vendor
[unix-history] / usr.bin / elvis / ex.c
CommitLineData
15637ed4
RG
1/* ex.c */
2
3/* Author:
4 * Steve Kirkendall
5 * 14407 SW Teal Blvd. #C
6 * Beaverton, OR 97005
7 * kirkenda@cs.pdx.edu
15637ed4
RG
8 */
9
10
11/* This file contains the code for reading ex commands. */
12
13#include "config.h"
14#include "ctype.h"
15#include "vi.h"
16
17/* This data type is used to describe the possible argument combinations */
18typedef 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 */
40
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")
45 */
46static struct
47{
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 */
52}
53 cmdnames[] =
54{ /* cmd name cmd code function arguments */
08746e8b
AM
55 {"print", CMD_PRINT, cmd_print, RANGE+NL },
56
15637ed4
RG
57 {"append", CMD_APPEND, cmd_append, FROM+ZERO+BANG },
58#ifdef DEBUG
59 {"bug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA+NL},
60#endif
61 {"change", CMD_CHANGE, cmd_append, RANGE+BANG },
62 {"delete", CMD_DELETE, cmd_delete, RANGE+WORD1 },
63 {"edit", CMD_EDIT, cmd_edit, BANG+FILE1+PLUS },
64 {"file", CMD_FILE, cmd_file, NAMEDF },
65 {"global", CMD_GLOBAL, cmd_global, RANGE+BANG+EXTRA+DFLALL+NOBAR},
66 {"insert", CMD_INSERT, cmd_append, FROM+BANG },
67 {"join", CMD_INSERT, cmd_join, RANGE+BANG },
68 {"k", CMD_MARK, cmd_mark, FROM+WORD1 },
69 {"list", CMD_LIST, cmd_print, RANGE+NL },
70 {"move", CMD_MOVE, cmd_move, RANGE+EXTRA },
71 {"next", CMD_NEXT, cmd_next, BANG+NAMEDFS },
72 {"Next", CMD_PREVIOUS, cmd_next, BANG },
15637ed4
RG
73 {"quit", CMD_QUIT, cmd_xit, BANG },
74 {"read", CMD_READ, cmd_read, FROM+ZERO+NAMEDF},
75 {"substitute", CMD_SUBSTITUTE, cmd_substitute, RANGE+EXTRA },
76 {"to", CMD_COPY, cmd_move, RANGE+EXTRA },
77 {"undo", CMD_UNDO, cmd_undo, NONE },
78 {"vglobal", CMD_VGLOBAL, cmd_global, RANGE+EXTRA+DFLALL+NOBAR},
79 {"write", CMD_WRITE, cmd_write, RANGE+BANG+FILE1+DFLALL},
80 {"xit", CMD_XIT, cmd_xit, BANG+NL },
81 {"yank", CMD_YANK, cmd_delete, RANGE+WORD1 },
82
83 {"!", CMD_BANG, cmd_shell, EXRCOK+RANGE+NAMEDFS+DFLNONE+NL+NOBAR},
84 {"#", CMD_NUMBER, cmd_print, RANGE+NL },
85 {"<", CMD_SHIFTL, cmd_shift, RANGE },
86 {">", CMD_SHIFTR, cmd_shift, RANGE },
87 {"=", CMD_EQUAL, cmd_file, RANGE },
88 {"&", CMD_SUBAGAIN, cmd_substitute, RANGE },
89#ifndef NO_AT
90 {"@", CMD_AT, cmd_at, EXTRA },
91#endif
92
93#ifndef NO_ABBR
94 {"abbreviate", CMD_ABBR, cmd_map, EXRCOK+BANG+EXTRA},
95#endif
96 {"args", CMD_ARGS, cmd_args, EXRCOK+NAMEDFS },
97#ifndef NO_ERRLIST
98 {"cc", CMD_CC, cmd_make, BANG+FILES },
99#endif
100 {"cd", CMD_CD, cmd_cd, EXRCOK+BANG+NAMEDF},
101 {"copy", CMD_COPY, cmd_move, RANGE+EXTRA },
102#ifndef NO_DIGRAPH
103 {"digraph", CMD_DIGRAPH, cmd_digraph, EXRCOK+BANG+EXTRA},
104#endif
105#ifndef NO_ERRLIST
106 {"errlist", CMD_ERRLIST, cmd_errlist, BANG+NAMEDF },
107#endif
108 {"ex", CMD_EDIT, cmd_edit, BANG+FILE1 },
109 {"mark", CMD_MARK, cmd_mark, FROM+WORD1 },
110#ifndef NO_MKEXRC
111 {"mkexrc", CMD_MKEXRC, cmd_mkexrc, NAMEDF },
112#endif
113 {"number", CMD_NUMBER, cmd_print, RANGE+NL },
08746e8b
AM
114#ifndef NO_TAGSTACK
115 {"pop", CMD_POP, cmd_pop, BANG+WORD1 },
116#endif
15637ed4
RG
117 {"put", CMD_PUT, cmd_put, FROM+ZERO+WORD1 },
118 {"set", CMD_SET, cmd_set, EXRCOK+EXTRA },
119 {"shell", CMD_SHELL, cmd_shell, NL },
120 {"source", CMD_SOURCE, cmd_source, EXRCOK+NAMEDF },
121#ifdef SIGTSTP
122 {"stop", CMD_STOP, cmd_suspend, NONE },
123#endif
124 {"tag", CMD_TAG, cmd_tag, BANG+WORD1 },
125 {"version", CMD_VERSION, cmd_version, EXRCOK+NONE },
126 {"visual", CMD_VISUAL, cmd_edit, BANG+NAMEDF },
08746e8b 127 {"wq", CMD_WQUIT, cmd_xit, NL },
15637ed4
RG
128
129#ifdef DEBUG
130 {"debug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA+NL},
131 {"validate", CMD_VALIDATE, cmd_validate, BANG+NL },
132#endif
133 {"chdir", CMD_CD, cmd_cd, EXRCOK+BANG+NAMEDF},
134#ifndef NO_COLOR
135 {"color", CMD_COLOR, cmd_color, EXRCOK+EXTRA },
136#endif
137#ifndef NO_ERRLIST
138 {"make", CMD_MAKE, cmd_make, BANG+NAMEDFS },
139#endif
140 {"map", CMD_MAP, cmd_map, EXRCOK+BANG+EXTRA},
141 {"previous", CMD_PREVIOUS, cmd_next, BANG },
142 {"rewind", CMD_REWIND, cmd_next, BANG },
143#ifdef SIGTSTP
144 {"suspend", CMD_SUSPEND, cmd_suspend, NONE },
145#endif
146 {"unmap", CMD_UNMAP, cmd_map, EXRCOK+BANG+EXTRA},
147#ifndef NO_ABBR
148 {"unabbreviate",CMD_UNABBR, cmd_map, EXRCOK+WORD1 },
149#endif
150
151 {(char *)0}
152};
153
154
155/* This function parses a search pattern - given a pointer to a / or ?,
156 * it replaces the ending / or ? with a \0, and returns a pointer to the
157 * stuff that came after the pattern.
158 */
159char *parseptrn(ptrn)
160 REG char *ptrn;
161{
162 REG char *scan;
163
164 for (scan = ptrn + 1;
165 *scan && *scan != *ptrn;
166 scan++)
167 {
168 /* allow backslashed versions of / and ? in the pattern */
169 if (*scan == '\\' && scan[1] != '\0')
170 {
171 scan++;
172 }
173 }
174 if (*scan)
175 {
176 *scan++ = '\0';
177 }
178
179 return scan;
180}
181
182
183/* This function parses a line specifier for ex commands */
184char *linespec(s, markptr)
185 REG char *s; /* start of the line specifier */
186 MARK *markptr; /* where to store the mark's value */
187{
188 long num;
189 REG char *t;
190
191 /* parse each ;-delimited clause of this linespec */
192 do
193 {
194 /* skip an initial ';', if any */
195 if (*s == ';')
196 {
197 s++;
198 }
199
200 /* skip leading spaces */
201 while (isspace(*s))
202 {
203 s++;
204 }
205
206 /* dot means current position */
207 if (*s == '.')
208 {
209 s++;
210 *markptr = cursor;
211 }
212 /* '$' means the last line */
213 else if (*s == '$')
214 {
215 s++;
216 *markptr = MARK_LAST;
217 }
218 /* digit means an absolute line number */
219 else if (isdigit(*s))
220 {
221 for (num = 0; isdigit(*s); s++)
222 {
223 num = num * 10 + *s - '0';
224 }
225 *markptr = MARK_AT_LINE(num);
226 }
227 /* appostrophe means go to a set mark */
228 else if (*s == '\'')
229 {
230 s++;
231 *markptr = m_tomark(cursor, 1L, (int)*s);
232 s++;
233 }
234 /* slash means do a search */
235 else if (*s == '/' || *s == '?')
236 {
237 /* put a '\0' at the end of the search pattern */
238 t = parseptrn(s);
239
240 /* search for the pattern */
241 *markptr &= ~(BLKSIZE - 1);
242 if (*s == '/')
243 {
244 pfetch(markline(*markptr));
245 if (plen > 0)
246 *markptr += plen - 1;
247 *markptr = m_fsrch(*markptr, s);
248 }
249 else
250 {
251 *markptr = m_bsrch(*markptr, s);
252 }
253
254 /* adjust command string pointer */
255 s = t;
256 }
257
258 /* if linespec was faulty, quit now */
259 if (!*markptr)
260 {
261 return s;
262 }
263
264 /* maybe add an offset */
265 t = s;
266 if (*t == '-' || *t == '+')
267 {
268 s++;
269 for (num = 0; isdigit(*s); s++)
270 {
271 num = num * 10 + *s - '0';
272 }
273 if (num == 0)
274 {
275 num = 1;
276 }
277 *markptr = m_updnto(*markptr, num, *t);
278 }
279 } while (*s == ';' || *s == '+' || *s == '-');
280
281 /* protect against invalid line numbers */
282 num = markline(*markptr);
283 if (num < 1L || num > nlines)
284 {
285 msg("Invalid line number -- must be from 1 to %ld", nlines);
286 *markptr = MARK_UNSET;
287 }
288
289 return s;
290}
291
292
293
294/* This function reads an ex command and executes it. */
295void ex()
296{
297 char cmdbuf[150];
298 REG int cmdlen;
299 static long oldline;
300
301 significant = FALSE;
302 oldline = markline(cursor);
303
304 while (mode == MODE_EX)
305 {
306 /* read a line */
307#ifdef CRUNCH
308 cmdlen = vgets(':', cmdbuf, sizeof(cmdbuf));
309#else
310 cmdlen = vgets(*o_prompt ? ':' : '\0', cmdbuf, sizeof(cmdbuf));
311#endif
312 if (cmdlen < 0)
313 {
314 return;
315 }
316
317 /* if empty line, assume ".+1" */
318 if (cmdlen == 0)
319 {
320 strcpy(cmdbuf, ".+1");
321 qaddch('\r');
322 clrtoeol();
323 }
324 else
325 {
326 addch('\n');
327 }
328 refresh();
329
330 /* parse & execute the command */
331 doexcmd(cmdbuf);
332
333 /* handle autoprint */
334 if (significant || markline(cursor) != oldline)
335 {
336 significant = FALSE;
337 oldline = markline(cursor);
338 if (*o_autoprint && mode == MODE_EX)
339 {
340 cmd_print(cursor, cursor, CMD_PRINT, FALSE, "");
341 }
342 }
343 }
344}
345
346void doexcmd(cmdbuf)
347 char *cmdbuf; /* string containing an ex command */
348{
349 REG char *scan; /* used to scan thru cmdbuf */
350 MARK frommark; /* first linespec */
351 MARK tomark; /* second linespec */
352 REG int cmdlen; /* length of the command name given */
353 CMD cmd; /* what command is this? */
354 ARGT argt; /* argument types for this command */
355 short forceit; /* bang version of a command? */
356 REG int cmdidx; /* index of command */
357 REG char *build; /* used while copying filenames */
358 int iswild; /* boolean: filenames use wildcards? */
359 int isdfl; /* using default line ranges? */
360 int didsub; /* did we substitute file names for % or # */
361
362 /* ex commands can't be undone via the shift-U command */
363 U_line = 0L;
364
365 /* permit extra colons at the start of the line */
366 for (; *cmdbuf == ':'; cmdbuf++)
367 {
368 }
369
370 /* ignore command lines that start with a double-quote */
371 if (*cmdbuf == '"')
372 {
373 return;
374 }
375 scan = cmdbuf;
376
377 /* parse the line specifier */
378 if (nlines < 1)
379 {
380 /* no file, so don't allow addresses */
381 }
382 else if (*scan == '%')
383 {
384 /* '%' means all lines */
385 frommark = MARK_FIRST;
386 tomark = MARK_LAST;
387 scan++;
388 }
389 else if (*scan == '0')
390 {
15637ed4 391 scan++;
08746e8b 392 frommark = tomark = (*scan ? MARK_UNSET : MARK_FIRST);
15637ed4
RG
393 }
394 else
395 {
396 frommark = cursor;
397 scan = linespec(scan, &frommark);
398 tomark = frommark;
399 if (frommark && *scan == ',')
400 {
401 scan++;
402 scan = linespec(scan, &tomark);
403 }
404 if (!tomark)
405 {
406 /* faulty line spec -- fault already described */
407 return;
408 }
409 if (frommark > tomark)
410 {
411 msg("first address exceeds the second");
412 return;
413 }
414 }
415 isdfl = (scan == cmdbuf);
416
417 /* skip whitespace */
418 while (isspace(*scan))
419 {
420 scan++;
421 }
422
08746e8b
AM
423 /* Figure out how long the command name is. If no command, then the
424 * length is 0, which will match the "print" command.
425 */
15637ed4
RG
426 if (!*scan)
427 {
08746e8b
AM
428 /* if not in ex mode, and both endpoints are at the line,
429 * then just move to the start of that line without printing
430 */
431 if (mode != MODE_EX && frommark == tomark)
432 {
433 if (tomark != MARK_UNSET)
434 cursor = tomark;
435 return;
436 }
437 cmdlen = 0;
15637ed4 438 }
08746e8b 439 else if (!isalpha(*scan))
15637ed4
RG
440 {
441 cmdlen = 1;
442 }
443 else
444 {
445 for (cmdlen = 1;
446 isalpha(scan[cmdlen]);
447 cmdlen++)
448 {
449 }
450 }
451
452 /* lookup the command code */
453 for (cmdidx = 0;
454 cmdnames[cmdidx].name && strncmp(scan, cmdnames[cmdidx].name, cmdlen);
455 cmdidx++)
456 {
457 }
458 argt = cmdnames[cmdidx].argt;
459 cmd = cmdnames[cmdidx].code;
460 if (cmd == CMD_NULL)
461 {
462 msg("Unknown command \"%.*s\"", cmdlen, scan);
463 return;
464 }
465
466 /* !!! if the command doesn't have NOBAR set, then replace | with \0 */
467
468 /* if the command ended with a bang, set the forceit flag */
469 scan += cmdlen;
470 if ((argt & BANG) && *scan == '!')
471 {
472 scan++;
473 forceit = 1;
474 }
475 else
476 {
477 forceit = 0;
478 }
479
480 /* skip any more whitespace, to leave scan pointing to arguments */
481 while (isspace(*scan))
482 {
483 scan++;
484 }
485
486 /* a couple of special cases for filenames */
487 if (argt & XFILE)
488 {
489 /* if names were given, process them */
490 if (*scan)
491 {
492 for (build = tmpblk.c, iswild = didsub = FALSE; *scan; scan++)
493 {
494 switch (*scan)
495 {
496 case '\\':
497 if (scan[1] == '\\' || scan[1] == '%' || scan[1] == '#')
498 {
499 *build++ = *++scan;
500 }
501 else
502 {
503 *build++ = '\\';
504 }
505 break;
506
507 case '%':
508 if (!*origname)
509 {
510 msg("No filename to substitute for %%");
511 return;
512 }
513 strcpy(build, origname);
514 while (*build)
515 {
516 build++;
517 }
518 didsub = TRUE;
519 break;
520
521 case '#':
522 if (!*prevorig)
523 {
524 msg("No filename to substitute for #");
525 return;
526 }
527 strcpy(build, prevorig);
528 while (*build)
529 {
530 build++;
531 }
532 didsub = TRUE;
533 break;
534
535 case '*':
536 case '?':
537#if !(MSDOS || TOS)
538 case '[':
539 case '`':
540 case '{': /* } */
541 case '$':
542 case '~':
543#endif
544 *build++ = *scan;
545 iswild = TRUE;
546 break;
547
548 default:
549 *build++ = *scan;
550 }
551 }
552 *build = '\0';
553
554 if (cmd == CMD_BANG
555 || cmd == CMD_READ && tmpblk.c[0] == '!'
556 || cmd == CMD_WRITE && tmpblk.c[0] == '!')
557 {
558 if (didsub)
559 {
560 if (mode != MODE_EX)
561 {
562 addch('\n');
563 }
564 addstr(tmpblk.c);
565 addch('\n');
566 exrefresh();
567 }
568 }
569 else
570 {
571 if (iswild && tmpblk.c[0] != '>')
572 {
573 scan = wildcard(tmpblk.c);
574 }
575 }
576 }
577 else /* no names given, maybe assume origname */
578 {
579 if (!(argt & NODFL))
580 {
581 strcpy(tmpblk.c, origname);
582 }
583 else
584 {
585 *tmpblk.c = '\0';
586 }
587 }
588
589 scan = tmpblk.c;
590 }
591
592 /* bad arguments? */
593 if (!(argt & EXRCOK) && nlines < 1L)
594 {
595 msg("Can't use the \"%s\" command in a %s file", cmdnames[cmdidx].name, EXRC);
596 return;
597 }
598 if (!(argt & (ZERO | EXRCOK)) && frommark == MARK_UNSET)
599 {
600 msg("Can't use address 0 with \"%s\" command.", cmdnames[cmdidx].name);
601 return;
602 }
603 if (!(argt & FROM) && frommark != cursor && nlines >= 1L)
604 {
605 msg("Can't use address with \"%s\" command.", cmdnames[cmdidx].name);
606 return;
607 }
608 if (!(argt & TO) && tomark != frommark && nlines >= 1L)
609 {
610 msg("Can't use a range with \"%s\" command.", cmdnames[cmdidx].name);
611 return;
612 }
613 if (!(argt & EXTRA) && *scan)
614 {
615 msg("Extra characters after \"%s\" command.", cmdnames[cmdidx].name);
616 return;
617 }
618 if ((argt & NOSPC) && !(cmd == CMD_READ && (forceit || *scan == '!')))
619 {
620 build = scan;
621#ifndef CRUNCH
622 if ((argt & PLUS) && *build == '+')
623 {
624 while (*build && !isspace(*build))
625 {
626 build++;
627 }
628 while (*build && isspace(*build))
629 {
630 build++;
631 }
632 }
633#endif /* not CRUNCH */
634 for (; *build; build++)
635 {
636 if (isspace(*build))
637 {
638 msg("Too many %s to \"%s\" command.",
639 (argt & XFILE) ? "filenames" : "arguments",
640 cmdnames[cmdidx].name);
641 return;
642 }
643 }
644 }
645
646 /* some commands have special default ranges */
647 if (isdfl && (argt & DFLALL))
648 {
649 frommark = MARK_FIRST;
650 tomark = MARK_LAST;
651 }
652 else if (isdfl && (argt & DFLNONE))
653 {
654 frommark = tomark = 0L;
655 }
656
657 /* write a newline if called from visual mode */
658 if ((argt & NL) && mode != MODE_EX && !exwrote)
659 {
660 addch('\n');
661 exrefresh();
662 }
663
664 /* act on the command */
665 (*cmdnames[cmdidx].fn)(frommark, tomark, cmd, forceit, scan);
666}
667
668
669/* This function executes EX commands from a file. It returns 1 normally, or
670 * 0 if the file could not be opened for reading.
671 */
672int doexrc(filename)
673 char *filename; /* name of a ".exrc" file */
674{
675 int fd; /* file descriptor */
676 int len; /* length of the ".exrc" file */
677
08746e8b
AM
678#ifdef CRUNCH
679 /* small address space - we need to conserve space */
680
15637ed4
RG
681 /* !!! kludge: we use U_text as the buffer. This has the side-effect
682 * of interfering with the shift-U visual command. Disable shift-U.
683 */
684 U_line = 0L;
08746e8b
AM
685#else
686# if TINYSTACK
687# if TOS || MINT
688 /* small stack, but big heap. Allocate buffer from heap */
689 char *U_text = (char *)malloc(4096);
690 if (!U_text)
691 {
692 return 0;
693 }
694# else
695 /* small stack - we need to conserve space */
696
697 /* !!! kludge: we use U_text as the buffer. This has the side-effect
698 * of interfering with the shift-U visual command. Disable shift-U.
699 */
700 U_line = 0L;
701# endif
702# else
703 /* This is how we would *like* to do it -- with a large buffer on the
704 * stack, so we can handle large .exrc files and also recursion.
705 */
706 char U_text[4096];
707# endif
708#endif
15637ed4
RG
709
710 /* open the file, read it, and close */
711 fd = open(filename, O_RDONLY);
712 if (fd < 0)
713 {
08746e8b
AM
714#if TINYSTACK && (TOS || MINT)
715 free(U_text);
716#endif
15637ed4
RG
717 return 0;
718 }
08746e8b
AM
719#if TINYSTACK && (TOS || MINT)
720 len = tread(fd, U_text, 4096);
721#else
722 len = tread(fd, U_text, sizeof U_text);
723#endif
15637ed4
RG
724 close(fd);
725
726 /* execute the string */
727 exstring(U_text, len, ctrl('V'));
728
08746e8b
AM
729#if TINYSTACK && (TOS || MINT)
730 free(U_text);
731#endif
15637ed4
RG
732 return 1;
733}
734
735/* This function executes EX commands from a string. The commands may be
736 * separated by newlines or by | characters. It also handles quoting.
737 * Each individual command is limited to 132 bytes, but the total string
738 * may be longer.
739 */
740void exstring(buf, len, qchar)
741 char *buf; /* the commands to execute */
742 int len; /* the length of the string */
743 int qchar; /* the quote character -- ^V for file, or \ for kbd */
744{
745 char single[133]; /* a single command */
746 char *src, *dest;
747 int i;
748
749 /* find & do each command */
750 for (src = buf; src < &buf[len]; src++)
751 {
752 /* Copy a single command into single[]. Convert any quoted |
753 * into a normal |, and stop at a newline or unquoted |.
754 */
755 for (dest = single, i = 0;
756 i < 132 && src < &buf[len] && *src != '\n' && *src != '|';
757 src++, i++)
758 {
759 if (src[0] == qchar && src[1] == '|')
760 {
761 src++;
762 }
763 *dest++ = *src;
764 }
765 *dest = '\0';
766
767 /* do it */
768 doexcmd(single);
769 }
770}