Initial revision
[nvi.git] / ex / ex.c
blobd6f215c65aee3d4369e24984e494cdc5683e94b8
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 FILES (XFILE + EXTRA) /* multiple extra files allowed */
33 #define WORD1 (EXTRA + NOSPC) /* one extra word allowed */
34 #define FILE1 (FILES + NOSPC) /* 1 file allowed, defaults to current file */
35 #define NAMEDF (FILE1 + NODFL) /* 1 file allowed, defaults to "" */
36 #define NAMEDFS (FILES + NODFL) /* multiple files allowed, default is "" */
37 #define RANGE (FROM + TO) /* range of linespecs allowed */
38 #define NONE 0 /* no args allowed at all */
40 /* This array maps ex command names to command codes. The order in which
41 * command names are listed below is significant -- ambiguous abbreviations
42 * are always resolved to be the first possible match. (e.g. "r" is taken
43 * to mean "read", not "rewind", because "read" comes before "rewind")
45 static struct
47 char *name; /* name of the command */
48 CMD code; /* enum code of the command */
49 void (*fn)();/* function which executes the command */
50 ARGT argt; /* command line arguments permitted/needed/used */
52 cmdnames[] =
53 { /* cmd name cmd code function arguments */
54 {"append", CMD_APPEND, cmd_append, FROM+ZERO },
55 #ifdef DEBUG
56 {"bug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA+NL},
57 #endif
58 {"change", CMD_CHANGE, cmd_append, RANGE },
59 {"delete", CMD_DELETE, cmd_delete, RANGE+WORD1 },
60 {"edit", CMD_EDIT, cmd_edit, BANG+FILE1+PLUS },
61 {"file", CMD_FILE, cmd_file, NAMEDF },
62 {"global", CMD_GLOBAL, cmd_global, RANGE+BANG+EXTRA+DFLALL},
63 {"insert", CMD_INSERT, cmd_append, FROM },
64 {"join", CMD_INSERT, cmd_join, RANGE },
65 {"k", CMD_MARK, cmd_mark, FROM+WORD1 },
66 {"list", CMD_LIST, cmd_print, RANGE+NL },
67 {"move", CMD_MOVE, cmd_move, RANGE+EXTRA },
68 {"next", CMD_NEXT, cmd_next, BANG+NAMEDFS },
69 {"Next", CMD_PREVIOUS, cmd_next, BANG },
70 {"print", CMD_PRINT, cmd_print, RANGE+NL },
71 {"quit", CMD_QUIT, cmd_xit, BANG },
72 {"read", CMD_READ, cmd_read, FROM+ZERO+NAMEDF},
73 {"substitute", CMD_SUBSTITUTE, cmd_substitute, RANGE+EXTRA },
74 {"to", CMD_COPY, cmd_move, RANGE+EXTRA },
75 {"undo", CMD_UNDO, cmd_undo, NONE },
76 {"vglobal", CMD_VGLOBAL, cmd_global, RANGE+EXTRA+DFLALL},
77 {"write", CMD_WRITE, cmd_write, RANGE+BANG+FILE1+DFLALL},
78 {"xit", CMD_XIT, cmd_xit, BANG+NL },
79 {"yank", CMD_YANK, cmd_delete, RANGE+WORD1 },
81 {"!", CMD_BANG, cmd_shell, EXRCOK+RANGE+NAMEDFS+DFLNONE+NL},
82 {"<", CMD_SHIFTL, cmd_shift, RANGE },
83 {">", CMD_SHIFTR, cmd_shift, RANGE },
84 {"=", CMD_EQUAL, cmd_file, RANGE },
85 {"&", CMD_SUBAGAIN, cmd_substitute, RANGE },
86 #ifndef NO_AT
87 {"@", CMD_AT, cmd_at, EXTRA },
88 #endif
90 #ifndef NO_ABBR
91 {"abbreviate", CMD_ABBR, cmd_abbr, EXRCOK+EXTRA },
92 #endif
93 {"args", CMD_ARGS, cmd_args, EXRCOK+NAMEDFS },
94 #ifndef NO_ERRLIST
95 {"cc", CMD_CC, cmd_make, BANG+FILES },
96 #endif
97 {"cd", CMD_CD, cmd_cd, EXRCOK+NAMEDF },
98 {"copy", CMD_COPY, cmd_move, RANGE+EXTRA },
99 #ifndef NO_DIGRAPH
100 {"digraph", CMD_DIGRAPH, cmd_digraph, EXRCOK+BANG+EXTRA},
101 #endif
102 #ifndef NO_ERRLIST
103 {"errlist", CMD_ERRLIST, cmd_errlist, BANG+NAMEDF },
104 #endif
105 {"ex", CMD_EDIT, cmd_edit, BANG+FILE1 },
106 #ifdef SYSV_COMPAT
107 {"mark", CMD_MARK, cmd_mark, FROM+WORD1 },
108 #endif
109 {"map", CMD_MAP, cmd_map, EXRCOK+BANG+EXTRA},
110 #ifndef NO_MKEXRC
111 {"mkexrc", CMD_MKEXRC, cmd_mkexrc, NAMEDF },
112 #endif
113 {"number", CMD_NUMBER, cmd_print, RANGE+NL },
114 {"put", CMD_PUT, cmd_put, FROM+ZERO+WORD1 },
115 {"set", CMD_SET, cmd_set, EXRCOK+EXTRA },
116 {"shell", CMD_SHELL, cmd_shell, NL },
117 {"source", CMD_SOURCE, cmd_source, EXRCOK+NAMEDF },
118 {"tag", CMD_TAG, cmd_tag, BANG+WORD1 },
119 {"version", CMD_VERSION, cmd_version, EXRCOK+NONE },
120 {"visual", CMD_VISUAL, cmd_edit, BANG+NAMEDF },
121 {"wq", CMD_WQUIT, cmd_xit, NL },
123 #ifdef DEBUG
124 {"debug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA+NL},
125 {"validate", CMD_VALIDATE, cmd_validate, BANG+NL },
126 #endif
127 {"chdir", CMD_CD, cmd_cd, EXRCOK+NAMEDF },
128 #ifndef NO_COLOR
129 {"color", CMD_COLOR, cmd_color, EXRCOK+EXTRA },
130 #endif
131 #ifndef NO_ERRLIST
132 {"make", CMD_MAKE, cmd_make, BANG+NAMEDFS },
133 #endif
134 #ifndef SYSV_COMPAT
135 {"mark", CMD_MARK, cmd_mark, FROM+WORD1 },
136 #endif
137 {"previous", CMD_PREVIOUS, cmd_next, BANG },
138 {"rewind", CMD_REWIND, cmd_next, BANG },
139 {"unmap", CMD_UNMAP, cmd_map, EXRCOK+BANG+EXTRA},
140 #ifndef NO_ABBR
141 {"unabbreviate",CMD_UNABBR, cmd_abbr, EXRCOK+WORD1 },
142 #endif
144 {(char *)0}
148 /* This function parses a search pattern - given a pointer to a / or ?,
149 * it replaces the ending / or ? with a \0, and returns a pointer to the
150 * stuff that came after the pattern.
152 char *parseptrn(ptrn)
153 REG char *ptrn;
155 REG char *scan;
157 for (scan = ptrn + 1;
158 *scan && *scan != *ptrn;
159 scan++)
161 /* allow backslashed versions of / and ? in the pattern */
162 if (*scan == '\\' && scan[1] != '\0')
164 scan++;
167 if (*scan)
169 *scan++ = '\0';
172 return scan;
176 /* This function parses a line specifier for ex commands */
177 char *linespec(s, markptr)
178 REG char *s; /* start of the line specifier */
179 MARK *markptr; /* where to store the mark's value */
181 long num;
182 REG char *t;
184 /* parse each ;-delimited clause of this linespec */
187 /* skip an initial ';', if any */
188 if (*s == ';')
190 s++;
193 /* skip leading spaces */
194 while (isspace(*s))
196 s++;
199 /* dot means current position */
200 if (*s == '.')
202 s++;
203 *markptr = cursor;
205 /* '$' means the last line */
206 else if (*s == '$')
208 s++;
209 *markptr = MARK_LAST;
211 /* digit means an absolute line number */
212 else if (isdigit(*s))
214 for (num = 0; isdigit(*s); s++)
216 num = num * 10 + *s - '0';
218 *markptr = MARK_AT_LINE(num);
220 /* appostrophe means go to a set mark */
221 else if (*s == '\'')
223 s++;
224 *markptr = m_tomark(cursor, 1L, (int)*s);
225 s++;
227 /* slash means do a search */
228 else if (*s == '/' || *s == '?')
230 /* put a '\0' at the end of the search pattern */
231 t = parseptrn(s);
233 /* search for the pattern */
234 *markptr &= ~(BLKSIZE - 1);
235 if (*s == '/')
237 pfetch(markline(*markptr));
238 if (plen > 0)
239 *markptr += plen - 1;
240 *markptr = m_fsrch(*markptr, s);
242 else
244 *markptr = m_bsrch(*markptr, s);
247 /* adjust command string pointer */
248 s = t;
251 /* if linespec was faulty, quit now */
252 if (!*markptr)
254 return s;
257 /* maybe add an offset */
258 t = s;
259 if (*t == '-' || *t == '+')
261 s++;
262 for (num = 0; isdigit(*s); s++)
264 num = num * 10 + *s - '0';
266 if (num == 0)
268 num = 1;
270 *markptr = m_updnto(*markptr, num, *t);
272 } while (*s == ';' || *s == '+' || *s == '-');
274 /* protect against invalid line numbers */
275 num = markline(*markptr);
276 if (num < 1L || num > nlines)
278 msg("Invalid line number -- must be from 1 to %ld", nlines);
279 *markptr = MARK_UNSET;
282 return s;
287 /* This function reads an ex command and executes it. */
288 void ex()
290 char cmdbuf[150];
291 REG int cmdlen;
292 static long oldline;
294 significant = FALSE;
295 oldline = markline(cursor);
297 while (mode == MODE_EX)
299 /* read a line */
300 #ifdef CRUNCH
301 cmdlen = vgets(':', cmdbuf, sizeof(cmdbuf));
302 #else
303 cmdlen = vgets(*o_prompt ? ':' : '\0', cmdbuf, sizeof(cmdbuf));
304 #endif
305 if (cmdlen < 0)
307 return;
310 /* if empty line, assume ".+1" */
311 if (cmdlen == 0)
313 strcpy(cmdbuf, ".+1");
314 qaddch('\r');
315 clrtoeol();
317 else
319 addch('\n');
321 refresh();
323 /* parse & execute the command */
324 doexcmd(cmdbuf);
326 /* handle autoprint */
327 if (significant || markline(cursor) != oldline)
329 significant = FALSE;
330 oldline = markline(cursor);
331 if (*o_autoprint && mode == MODE_EX)
333 cmd_print(cursor, cursor, CMD_PRINT, FALSE, "");
339 void doexcmd(cmdbuf)
340 char *cmdbuf; /* string containing an ex command */
342 REG char *scan; /* used to scan thru cmdbuf */
343 MARK frommark; /* first linespec */
344 MARK tomark; /* second linespec */
345 REG int cmdlen; /* length of the command name given */
346 CMD cmd; /* what command is this? */
347 ARGT argt; /* argument types for this command */
348 short forceit; /* bang version of a command? */
349 REG int cmdidx; /* index of command */
350 REG char *build; /* used while copying filenames */
351 int iswild; /* boolean: filenames use wildcards? */
352 int isdfl; /* using default line ranges? */
353 int didsub; /* did we substitute file names for % or # */
355 /* ex commands can't be undone via the shift-U command */
356 U_line = 0L;
358 /* permit extra colons at the start of the line */
359 for (; *cmdbuf == ':'; cmdbuf++)
363 /* ignore command lines that start with a double-quote */
364 if (*cmdbuf == '"')
366 return;
368 scan = cmdbuf;
370 /* parse the line specifier */
371 if (nlines < 1)
373 /* no file, so don't allow addresses */
375 else if (*scan == '%')
377 /* '%' means all lines */
378 frommark = MARK_FIRST;
379 tomark = MARK_LAST;
380 scan++;
382 else if (*scan == '0')
384 frommark = tomark = MARK_UNSET;
385 scan++;
387 else
389 frommark = cursor;
390 scan = linespec(scan, &frommark);
391 tomark = frommark;
392 if (frommark && *scan == ',')
394 scan++;
395 scan = linespec(scan, &tomark);
397 if (!tomark)
399 /* faulty line spec -- fault already described */
400 return;
402 if (frommark > tomark)
404 msg("first address exceeds the second");
405 return;
408 isdfl = (scan == cmdbuf);
410 /* skip whitespace */
411 while (isspace(*scan))
413 scan++;
416 /* if no command, then just move the cursor to the mark */
417 if (!*scan)
419 if (tomark != MARK_UNSET)
420 cursor = tomark;
421 return;
424 /* figure out how long the command name is */
425 if (!isalpha(*scan))
427 cmdlen = 1;
429 else
431 for (cmdlen = 1;
432 isalpha(scan[cmdlen]);
433 cmdlen++)
438 /* lookup the command code */
439 for (cmdidx = 0;
440 cmdnames[cmdidx].name && strncmp(scan, cmdnames[cmdidx].name, cmdlen);
441 cmdidx++)
444 argt = cmdnames[cmdidx].argt;
445 cmd = cmdnames[cmdidx].code;
446 if (cmd == CMD_NULL)
448 msg("Unknown command \"%.*s\"", cmdlen, scan);
449 return;
452 /* if the command ended with a bang, set the forceit flag */
453 scan += cmdlen;
454 if ((argt & BANG) && *scan == '!')
456 scan++;
457 forceit = 1;
459 else
461 forceit = 0;
464 /* skip any more unquoted whitespace, to leave scan pointing to arguments */
465 while (isspace(*scan) && !QTST(scan))
467 scan++;
470 /* a couple of special cases for filenames */
471 if (argt & XFILE)
473 /* if names were given, process them */
474 if (*scan)
476 for (build = tmpblk.c, iswild = didsub = FALSE; *scan; scan++)
478 switch (QTST(scan) ? '\0' : *scan)
480 case '%':
481 if (!*origname)
483 msg("No filename to substitute for %%");
484 return;
486 strcpy(build, origname);
487 while (*build)
489 build++;
491 didsub = TRUE;
492 break;
494 case '#':
495 if (!*prevorig)
497 msg("No filename to substitute for #");
498 return;
500 strcpy(build, prevorig);
501 while (*build)
503 build++;
505 didsub = TRUE;
506 break;
508 case '*':
509 case '?':
510 #if !(MSDOS || TOS)
511 case '[':
512 case '`':
513 case '{': /* } */
514 case '$':
515 case '~':
516 #endif
517 *build++ = *scan;
518 iswild = TRUE;
519 break;
521 default:
522 *build++ = *scan;
525 *build = '\0';
527 if (cmd == CMD_BANG
528 || cmd == CMD_READ && tmpblk.c[0] == '!'
529 || cmd == CMD_WRITE && tmpblk.c[0] == '!')
531 if (didsub)
533 if (mode != MODE_EX)
535 addch('\n');
537 addstr(tmpblk.c);
538 addch('\n');
539 exrefresh();
542 else
544 if (iswild && tmpblk.c[0] != '>')
546 scan = wildcard(tmpblk.c);
550 else /* no names given, maybe assume origname */
552 if (!(argt & NODFL))
554 strcpy(tmpblk.c, origname);
556 else
558 *tmpblk.c = '\0';
562 scan = tmpblk.c;
565 /* bad arguments? */
566 if (!(argt & EXRCOK) && nlines < 1L)
568 msg("Can't use the \"%s\" command in a %s file", cmdnames[cmdidx].name, EXRC);
569 return;
571 if (!(argt & (ZERO | EXRCOK)) && frommark == MARK_UNSET)
573 msg("Can't use address 0 with \"%s\" command.", cmdnames[cmdidx].name);
574 return;
576 if (!(argt & FROM) && frommark != cursor && nlines >= 1L)
578 msg("Can't use address with \"%s\" command.", cmdnames[cmdidx].name);
579 return;
581 if (!(argt & TO) && tomark != frommark && nlines >= 1L)
583 msg("Can't use a range with \"%s\" command.", cmdnames[cmdidx].name);
584 return;
586 if (!(argt & EXTRA) && *scan)
588 msg("Extra characters after \"%s\" command.", cmdnames[cmdidx].name);
589 return;
591 if ((argt & NOSPC) && !(cmd == CMD_READ && (forceit || *scan == '!')))
593 build = scan;
594 #ifndef CRUNCH
595 if ((argt & PLUS) && *build == '+')
597 while (*build && !isspace(*build))
599 build++;
601 while (*build && isspace(*build))
603 build++;
606 #endif /* not CRUNCH */
607 for (; *build; build++)
609 if (isspace(*build))
611 msg("Too many %s to \"%s\" command.",
612 (argt & XFILE) ? "filenames" : "arguments",
613 cmdnames[cmdidx].name);
614 return;
619 /* some commands have special default ranges */
620 if (isdfl && (argt & DFLALL))
622 frommark = MARK_FIRST;
623 tomark = MARK_LAST;
625 else if (isdfl && (argt & DFLNONE))
627 frommark = tomark = 0L;
630 /* write a newline if called from visual mode */
631 if ((argt & NL) && mode != MODE_EX && !exwrote)
633 addch('\n');
634 exrefresh();
637 /* act on the command */
638 (*cmdnames[cmdidx].fn)(frommark, tomark, cmd, forceit, scan);
642 /* This function executes EX commands from a file. It returns 1 normally, or
643 * 0 if the file could not be opened for reading.
645 int doexrc(filename)
646 char *filename; /* name of a ".exrc" file */
648 int fd; /* file descriptor */
649 int len; /* length of the ".exrc" file */
651 /* !!! kludge: we use U_text as the buffer. This has the side-effect
652 * of interfering with the shift-U visual command. Disable shift-U.
654 U_line = 0L;
656 /* open the file, read it, and close */
657 fd = open(filename, O_RDONLY);
658 if (fd < 0)
660 return 0;
662 len = tread(fd, U_text, BLKSIZE);
663 close(fd);
665 /* execute the string */
666 exstring(U_text, len);
668 return 1;
671 /* This function executes EX commands from a string. The commands may be
672 * separated by newlines or by | characters. It also handles quoting.
673 * each individual command is limited to 132 bytes, but the total string
674 * may be longer.
676 void exstring(buf, len)
677 char *buf; /* the commands to execute */
678 int len; /* the length of the string */
680 char single[133]; /* a single command */
681 char *src, *dest;
682 int i;
684 /* find & do each command */
685 for (src = buf; src < &buf[len]; src++)
687 /* Copy a single command into single[], checking for ^V quotes */
688 QSTART(single);
689 for (dest = single, i = 0;
690 i < 132 && src < &buf[len] && *src != '\n' && *src != '|';
691 src++, i++)
693 #ifndef NO_QUOTE
694 if (*src == ('V' & 0x1f) && src + 1 < buf + len && src[1] != '\n')
696 src++;
697 QSET(dest);
699 #endif
700 *dest++ = *src;
702 *dest = '\0';
703 QEND();
705 /* do it */
706 doexcmd(single);