1 /* ex.c */
2
3 /* Author:
4 * Steve Kirkendall
5 * 16820 SW Tallac Way
6 * Beaverton, OR 97006
7 * kirkenda@jove.cs.pdx.edu, or ...uunet!tektronix!psueea!jove!kirkenda
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 #ifndef isascii
18 # define isascii(c) !((c)&~0x7f)
19 #endif
20
21 /* This data type is used to describe the possible argument combinations */
22 typedef short ARGT;
23 #define FROM 1 /* allow a linespec */
24 #define TO 2 /* allow a second linespec */
25 #define BANG 4 /* allow a ! after the command name */
26 #define EXTRA 8 /* allow extra args after command name */
27 #define XFILE 16 /* expand wildcards in extra part */
28 #define NOSPC 32 /* no spaces allowed in the extra part */
29 #define DFLALL 64 /* default file range is 1,$ */
30 #define DFLNONE 128 /* no default file range */
31 #define NODFL 256 /* do not default to the current file name */
32 #define EXRCOK 512 /* can be in a .exrc file */
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 */
46 static 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 */
55 {"append", CMD_APPEND, cmd_append, FROM },
56 #ifdef DEBUG
57 {"bug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA},
58 #endif
59 {"change", CMD_CHANGE, cmd_append, RANGE },
60 {"delete", CMD_DELETE, cmd_delete, RANGE+WORD1 },
61 {"edit", CMD_EDIT, cmd_edit, BANG+FILE1 },
62 {"file", CMD_FILE, cmd_file, NONE },
63 {"global", CMD_GLOBAL, cmd_global, RANGE+BANG+EXTRA+DFLALL},
64 {"insert", CMD_INSERT, cmd_append, FROM },
65 {"join", CMD_INSERT, cmd_join, RANGE },
66 {"k", CMD_MARK, cmd_mark, FROM+WORD1 },
67 {"list", CMD_LIST, cmd_list, RANGE },
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 },
72 {"quit", CMD_QUIT, cmd_quit, BANG },
73 {"read", CMD_READ, cmd_read, FROM+BANG+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},
78 {"write", CMD_WRITE, cmd_write, RANGE+BANG+FILE1+DFLALL},
79 {"xit", CMD_XIT, cmd_xit, BANG },
80 {"yank", CMD_YANK, cmd_delete, RANGE+WORD1 },
81
82 {"!", CMD_BANG, cmd_shell, EXRCOK+RANGE+NAMEDFS+DFLNONE},
83 {"<", CMD_SHIFTL, cmd_shift, RANGE },
84 {">", CMD_SHIFTR, cmd_shift, RANGE },
85 {"=", CMD_FILE, cmd_file, RANGE },
86
87 {"args", CMD_ARGS, cmd_args, EXRCOK+NAMEDFS },
88 {"cd", CMD_CD, cmd_cd, EXRCOK+NAMEDF },
89 {"copy", CMD_COPY, cmd_move, RANGE+EXTRA },
90 #ifndef NO_DIGRAPH
91 {"digraph", CMD_DIGRAPH, cmd_digraph, EXRCOK+BANG+EXTRA},
92 #endif
93 {"ex", CMD_EDIT, cmd_edit, BANG+FILE1 },
94 {"map", CMD_MAP, cmd_map, EXRCOK+BANG+EXTRA},
95 #ifndef NO_EXTENSIONS
96 {"mkexrc", CMD_MKEXRC, cmd_mkexrc, NONE },
97 #endif
98 {"put", CMD_PUT, cmd_put, FROM+WORD1 },
99 {"set", CMD_SET, cmd_set, EXRCOK+EXTRA },
100 {"shell", CMD_SHELL, cmd_shell, NONE },
101 {"source", CMD_SOURCE, cmd_source, EXRCOK+NAMEDF },
102 {"tag", CMD_TAG, cmd_tag, BANG+WORD1 },
103 {"version", CMD_VERSION, cmd_version, EXRCOK+NONE },
104 {"visual", CMD_VISUAL, cmd_visual, NONE },
105 {"wq", CMD_WQUIT, cmd_xit, NONE },
106
107 #ifdef DEBUG
108 {"debug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA},
109 {"validate", CMD_VALIDATE, cmd_validate, BANG },
110 #endif
111 {"chdir", CMD_CD, cmd_cd, EXRCOK+NAMEDF },
112 {"mark", CMD_MARK, cmd_mark, FROM+WORD1 },
113 {"previous", CMD_PREVIOUS, cmd_next, BANG },
114 {"rewind", CMD_REWIND, cmd_next, BANG },
115 {"unmap", CMD_UNMAP, cmd_map, EXRCOK+BANG+EXTRA},
116
117 {(char *)0}
118 };
119
120
121 /* This function parses a search pattern - given a pointer to a / or ?,
122 * it replaces the ending / or ? with a \0, and returns a pointer to the
123 * stuff that came after the pattern.
124 */
parseptrn(ptrn)125 char *parseptrn(ptrn)
126 register char *ptrn;
127 {
128 register char *scan;
129
130
131 for (scan = ptrn + 1;
132 *scan && *scan != *ptrn;
133 scan++)
134 {
135 /* allow backslashed versions of / and ? in the pattern */
136 if (*scan == '\\' && scan[1] != '\0')
137 {
138 scan++;
139 }
140 }
141 if (*scan)
142 {
143 *scan++ = '\0';
144 }
145
146 return scan;
147 }
148
149
150 /* This function parses a line specifier for ex commands */
linespec(s,markptr)151 char *linespec(s, markptr)
152 register char *s; /* start of the line specifier */
153 MARK *markptr; /* where to store the mark's value */
154 {
155 long num;
156 register char *t;
157
158 /* parse each ;-delimited clause of this linespec */
159 do
160 {
161 /* skip an initial ';', if any */
162 if (*s == ';')
163 {
164 s++;
165 }
166
167 /* skip leading spaces */
168 while (isascii(*s) && isspace(*s))
169 {
170 s++;
171 }
172
173 /* dot means current position */
174 if (*s == '.')
175 {
176 s++;
177 *markptr = cursor;
178 }
179 /* '$' means the last line */
180 else if (*s == '$')
181 {
182 s++;
183 *markptr = m_toline(cursor, nlines);
184 }
185 /* digit means an absolute line number */
186 else if (isascii(*s) && isdigit(*s))
187 {
188 for (num = 0; isascii(*s) && isdigit(*s); s++)
189 {
190 num = num * 10 + *s - '0';
191 }
192 *markptr = m_toline(cursor, num);
193 }
194 /* appostrophe means go to a set mark */
195 else if (*s == '\'')
196 {
197 s++;
198 *markptr = m_tomark(cursor, 1L, (int)*s);
199 s++;
200 }
201 /* slash means do a search */
202 else if (*s == '/' || *s == '?')
203 {
204 /* put a '\0' at the end of the search pattern */
205 t = parseptrn(s);
206
207 /* search for the pattern */
208 if (*s == '/')
209 {
210 pfetch(markline(*markptr));
211 *markptr = (*markptr & ~(BLKSIZE - 1)) + plen - 1;
212 *markptr = m_fsrch(*markptr, s + 1);
213 }
214 else
215 {
216 *markptr &= ~(BLKSIZE - 1);
217 *markptr = m_bsrch(*markptr, s + 1);
218 }
219
220 /* adjust command string pointer */
221 s = t;
222 }
223
224 /* if linespec was faulty, quit now */
225 if (!*markptr)
226 {
227 return s;
228 }
229
230 /* maybe add an offset */
231 if (*s == '-')
232 {
233 s++;
234 for (num = 0; *s >= '0' && *s <= '9'; s++)
235 {
236 num = num * 10 + *s - '0';
237 }
238 if (num == 0)
239 {
240 num = 1;
241 }
242 *markptr = m_up(*markptr, num);
243 }
244 else if (*s == '+')
245 {
246 s++;
247 for (num = 0; *s >= '0' && *s <= '9'; s++)
248 {
249 num = num * 10 + *s - '0';
250 }
251 if (num == 0)
252 {
253 num = 1;
254 }
255 *markptr = m_down(*markptr, num);
256 }
257 } while (*s == ';' || *s == '+' || *s == '-');
258
259 return s;
260 }
261
262
263
264 /* This function reads an ex command and executes it. */
ex()265 ex()
266 {
267 char cmdbuf[80];
268 register int cmdlen;
269
270 /* read a line */
271 cmdlen = vgets(':', cmdbuf, sizeof cmdbuf);
272 if (cmdlen < 0)
273 {
274 return;
275 }
276 addch('\n');
277 refresh();
278
279 /* if empty line, assume ".+1" */
280 if (cmdlen == 0)
281 {
282 strcpy(cmdbuf, ".+1");
283 }
284
285 /* parse & execute the command */
286 doexcmd(cmdbuf);
287 }
288
doexcmd(cmdbuf)289 doexcmd(cmdbuf)
290 char *cmdbuf; /* string containing an ex command */
291 {
292 register char *scan; /* used to scan thru cmdbuf */
293 MARK frommark; /* first linespec */
294 MARK tomark; /* second linespec */
295 register int cmdlen; /* length of the command name given */
296 CMD cmd; /* what command is this? */
297 ARGT argt; /* argument types for this command */
298 short forceit; /* bang version of a command? */
299 register int cmdidx; /* index of command */
300 register char *build; /* used while copying filenames */
301 int iswild; /* boolean: filenames use wildcards? */
302 int isdfl; /* using default line ranges? */
303 int didsub; /* did we substitute file names for % or # */
304
305
306 /* ex commands can't be undone via the shift-U command */
307 U_line = 0L;
308
309 /* ignore command lines that start with "#" */
310 if (*cmdbuf == '#' || *cmdbuf == '"')
311 {
312 return;
313 }
314
315 /* permit extra colons at the start of the line */
316 while (*cmdbuf == ':')
317 {
318 cmdbuf++;
319 }
320
321 /* parse the line specifier */
322 scan = cmdbuf;
323 if (nlines < 1)
324 {
325 /* no file, so don't allow addresses */
326 }
327 else if (*scan == '%')
328 {
329 /* '%' means all lines */
330 frommark = m_toline(cursor, 1L);
331 tomark = m_toline(cursor, nlines);
332 scan++;
333 }
334 else
335 {
336 frommark = cursor;
337 scan = linespec(scan, &frommark);
338 tomark = frommark;
339 if (frommark && *scan == ',')
340 {
341 scan++;
342 scan = linespec(scan, &tomark);
343 }
344 if (!tomark)
345 {
346 /* faulty line spec -- fault already described */
347 return;
348 }
349 if (frommark > tomark)
350 {
351 msg("first address exceeds the second");
352 return;
353 }
354 }
355 isdfl = (scan == cmdbuf);
356
357 /* skip whitespace */
358 while (isascii(*scan) && isspace(*scan))
359 {
360 scan++;
361 }
362
363 /* if no command, then just move the cursor to the mark & print */
364 if (!*scan)
365 {
366 cursor = tomark;
367 if (mode != MODE_EX)
368 {
369 return;
370 }
371 scan = "p";
372 }
373
374 /* figure out how long the command name is */
375 if (isascii(*scan) && !isalpha(*scan))
376 {
377 cmdlen = 1;
378 }
379 else
380 {
381 for (cmdlen = 1;
382 !isascii(scan[cmdlen]) || isalpha(scan[cmdlen]);
383 cmdlen++)
384 {
385 }
386 }
387
388 /* lookup the command code */
389 for (cmdidx = 0;
390 cmdnames[cmdidx].name && strncmp(scan, cmdnames[cmdidx].name, cmdlen);
391 cmdidx++)
392 {
393 }
394 argt = cmdnames[cmdidx].argt;
395 cmd = cmdnames[cmdidx].code;
396 if (cmd == CMD_NULL)
397 {
398 msg("Unknown command \"%.*s\"", cmdlen, scan);
399 return;
400 }
401
402 /* if the command ended with a bang, set the forceit flag */
403 scan += cmdlen;
404 if ((argt & BANG) && *scan == '!')
405 {
406 scan++;
407 forceit = 1;
408 }
409 else
410 {
411 forceit = 0;
412 }
413
414 /* skip any more whitespace, to leave scan pointing to arguments */
415 while (isascii(*scan) && isspace(*scan))
416 {
417 scan++;
418 }
419
420 /* a couple of special cases for filenames */
421 if (argt & XFILE)
422 {
423 /* if names were given, process them */
424 if (*scan)
425 {
426 for (build = tmpblk.c, iswild = didsub = FALSE; *scan; scan++)
427 {
428 switch (*scan)
429 {
430 case '%':
431 if (!*origname)
432 {
433 msg("No filename to substitute for %");
434 return;
435 }
436 strcpy(build, origname);
437 while (*build)
438 {
439 build++;
440 }
441 didsub = TRUE;
442 break;
443
444 case '#':
445 if (!*prevorig)
446 {
447 msg("No filename to substitute for #");
448 return;
449 }
450 strcpy(build, prevorig);
451 while (*build)
452 {
453 build++;
454 }
455 didsub = TRUE;
456 break;
457
458 case '*':
459 case '?':
460 #if ! (MSDOS || TOS)
461 case '[':
462 case '`':
463 case '{': /* } */
464 case '$':
465 case '~':
466 #endif
467 *build++ = *scan;
468 iswild = TRUE;
469 break;
470
471 default:
472 *build++ = *scan;
473 }
474 }
475 *build = '\0';
476
477 if (cmd == CMD_BANG
478 || cmd == CMD_READ && (forceit || tmpblk.c[0] != '!'))
479 {
480 if (didsub)
481 {
482 addch('\n');
483 addstr(tmpblk.c);
484 addch('\n');
485 exrefresh();
486 }
487 }
488 else
489 {
490 if (iswild && tmpblk.c[0] != '>')
491 {
492 scan = wildcard(tmpblk.c);
493 }
494 }
495 }
496 else /* no names given, maybe assume origname */
497 {
498 if (!(argt & NODFL))
499 {
500 strcpy(tmpblk.c, origname);
501 }
502 else
503 {
504 *tmpblk.c = '\0';
505 }
506 }
507
508 scan = tmpblk.c;
509 }
510
511 /* bad arguments? */
512 if (!(argt & EXRCOK) && nlines < 1L)
513 {
514 msg("Can't use the \"%s\" command in a %s file", cmdnames[cmdidx].name, EXRC);
515 return;
516 }
517 if (!(argt & FROM) && frommark != cursor && nlines >= 1L)
518 {
519 msg("Can't use address with \"%s\" command.", cmdnames[cmdidx].name);
520 return;
521 }
522 if (!(argt & TO) && tomark != frommark && nlines >= 1L)
523 {
524 msg("Can't use a range with \"%s\" command.", cmdnames[cmdidx].name);
525 return;
526 }
527 if (!(argt & EXTRA) && *scan)
528 {
529 msg("Extra characters after \"%s\" command.", cmdnames[cmdidx].name);
530 return;
531 }
532 if ((argt & NOSPC) && !(cmd == CMD_READ && (forceit || *scan == '!')))
533 {
534 for (build = scan; *build; build++)
535 {
536 if (isspace(*build))
537 {
538 msg("Too many %s to \"%s\" command.",
539 (argt & XFILE) ? "filenames" : "arguments",
540 cmdnames[cmdidx].name);
541 return;
542 }
543 }
544 }
545
546 /* some commands have special default ranges */
547 if (isdfl && (argt & DFLALL))
548 {
549 frommark = MARK_FIRST;
550 tomark = MARK_LAST;
551 }
552 else if (isdfl && (argt & DFLNONE))
553 {
554 frommark = tomark = 0L;
555 }
556
557 /* act on the command */
558 (*cmdnames[cmdidx].fn)(frommark, tomark, cmd, forceit, scan);
559 }
560
561
562 /* This function executes EX commands from a file. It returns 1 normally, or
563 * 0 if the file could not be opened for reading.
564 */
doexrc(filename)565 int doexrc(filename)
566 char *filename; /* name of a ".exrc" file */
567 {
568 int fd; /* file descriptor */
569 int len; /* length of the ".exrc" file */
570 char *cmd; /* start of a command */
571 char *end; /* used to search for the end of cmd */
572 char buf[MAXRCLEN]; /* buffer, holds the entire .exrc file */
573
574 /* open the file, read it, and close */
575 fd = open(filename, O_RDONLY);
576 if (fd < 0)
577 {
578 return 0;
579 }
580 len = tread(fd, buf, MAXRCLEN);
581 close(fd);
582
583 /* find & do each command */
584 for (cmd = buf; cmd < &buf[len]; cmd = end + 1)
585 {
586 /* find the end of the command */
587 for (end = cmd; *end != '\n'; end++)
588 {
589 }
590 *end = '\0';
591
592 /* do it */
593 doexcmd(cmd);
594 }
595
596 return 1;
597 }
598