source: trunk/minix/commands/elvis/ex.c@ 15

Last change on this file since 15 was 9, checked in by Mattia Monga, 14 years ago

Minix 3.1.2a

File size: 16.0 KB
RevLine 
[9]1/* ex.c */
2
3/* Author:
4 * Steve Kirkendall
5 * 14407 SW Teal Blvd. #C
6 * Beaverton, OR 97005
7 * kirkenda@cs.pdx.edu
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 */
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 },
81
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
91
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 },
124
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
146
147 {(char *)0}
148};
149
150
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.
154 */
155char *parseptrn(ptrn)
156 REG char *ptrn;
157{
158 REG char *scan;
159
160 for (scan = ptrn + 1;
161 *scan && *scan != *ptrn;
162 scan++)
163 {
164 /* allow backslashed versions of / and ? in the pattern */
165 if (*scan == '\\' && scan[1] != '\0')
166 {
167 scan++;
168 }
169 }
170 if (*scan)
171 {
172 *scan++ = '\0';
173 }
174
175 return scan;
176}
177
178
179/* This function parses a line specifier for ex commands */
180char *linespec(s, markptr)
181 REG char *s; /* start of the line specifier */
182 MARK *markptr; /* where to store the mark's value */
183{
184 long num;
185 REG char *t;
186
187 /* parse each ;-delimited clause of this linespec */
188 do
189 {
190 /* skip an initial ';', if any */
191 if (*s == ';')
192 {
193 s++;
194 }
195
196 /* skip leading spaces */
197 while (isspace(*s))
198 {
199 s++;
200 }
201
202 /* dot means current position */
203 if (*s == '.')
204 {
205 s++;
206 *markptr = cursor;
207 }
208 /* '$' means the last line */
209 else if (*s == '$')
210 {
211 s++;
212 *markptr = MARK_LAST;
213 }
214 /* digit means an absolute line number */
215 else if (isdigit(*s))
216 {
217 for (num = 0; isdigit(*s); s++)
218 {
219 num = num * 10 + *s - '0';
220 }
221 *markptr = MARK_AT_LINE(num);
222 }
223 /* appostrophe means go to a set mark */
224 else if (*s == '\'')
225 {
226 s++;
227 *markptr = m_tomark(cursor, 1L, (int)*s);
228 s++;
229 }
230 /* slash means do a search */
231 else if (*s == '/' || *s == '?')
232 {
233 /* put a '\0' at the end of the search pattern */
234 t = parseptrn(s);
235
236 /* search for the pattern */
237 *markptr &= ~(BLKSIZE - 1);
238 if (*s == '/')
239 {
240 pfetch(markline(*markptr));
241 if (plen > 0)
242 *markptr += plen - 1;
243 *markptr = m_fsrch(*markptr, s);
244 }
245 else
246 {
247 *markptr = m_bsrch(*markptr, s);
248 }
249
250 /* adjust command string pointer */
251 s = t;
252 }
253
254 /* if linespec was faulty, quit now */
255 if (!*markptr)
256 {
257 return s;
258 }
259
260 /* maybe add an offset */
261 t = s;
262 if (*t == '-' || *t == '+')
263 {
264 s++;
265 for (num = 0; isdigit(*s); s++)
266 {
267 num = num * 10 + *s - '0';
268 }
269 if (num == 0)
270 {
271 num = 1;
272 }
273 *markptr = m_updnto(*markptr, num, *t);
274 }
275 } while (*s == ';' || *s == '+' || *s == '-');
276
277 /* protect against invalid line numbers */
278 num = markline(*markptr);
279 if (num < 1L || num > nlines)
280 {
281 msg("Invalid line number -- must be from 1 to %ld", nlines);
282 *markptr = MARK_UNSET;
283 }
284
285 return s;
286}
287
288
289
290/* This function reads an ex command and executes it. */
291void ex()
292{
293 char cmdbuf[150];
294 REG int cmdlen;
295 static long oldline;
296
297 significant = FALSE;
298 oldline = markline(cursor);
299
300 while (mode == MODE_EX)
301 {
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)
309 {
310 return;
311 }
312
313 /* if empty line, assume ".+1" */
314 if (cmdlen == 0)
315 {
316 strcpy(cmdbuf, ".+1");
317 qaddch('\r');
318 clrtoeol();
319 }
320 else
321 {
322 addch('\n');
323 }
324 refresh();
325
326 /* parse & execute the command */
327 doexcmd(cmdbuf);
328
329 /* handle autoprint */
330 if (significant || markline(cursor) != oldline)
331 {
332 significant = FALSE;
333 oldline = markline(cursor);
334 if (*o_autoprint && mode == MODE_EX)
335 {
336 cmd_print(cursor, cursor, CMD_PRINT, FALSE, "");
337 }
338 }
339 }
340}
341
342void doexcmd(cmdbuf)
343 char *cmdbuf; /* string containing an ex command */
344{
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 # */
357
358 /* ex commands can't be undone via the shift-U command */
359 U_line = 0L;
360
361 /* permit extra colons at the start of the line */
362 for (; *cmdbuf == ':'; cmdbuf++)
363 {
364 }
365
366 /* ignore command lines that start with a double-quote */
367 if (*cmdbuf == '"')
368 {
369 return;
370 }
371 scan = cmdbuf;
372
373 /* parse the line specifier */
374 if (nlines < 1)
375 {
376 /* no file, so don't allow addresses */
377 }
378 else if (*scan == '%')
379 {
380 /* '%' means all lines */
381 frommark = MARK_FIRST;
382 tomark = MARK_LAST;
383 scan++;
384 }
385 else if (*scan == '0')
386 {
387 frommark = tomark = MARK_UNSET;
388 scan++;
389 }
390 else
391 {
392 frommark = cursor;
393 scan = linespec(scan, &frommark);
394 tomark = frommark;
395 if (frommark && *scan == ',')
396 {
397 scan++;
398 scan = linespec(scan, &tomark);
399 }
400 if (!tomark)
401 {
402 /* faulty line spec -- fault already described */
403 return;
404 }
405 if (frommark > tomark)
406 {
407 msg("first address exceeds the second");
408 return;
409 }
410 }
411 isdfl = (scan == cmdbuf);
412
413 /* skip whitespace */
414 while (isspace(*scan))
415 {
416 scan++;
417 }
418
419 /* if no command, then just move the cursor to the mark */
420 if (!*scan)
421 {
422 if (tomark != MARK_UNSET)
423 cursor = tomark;
424 return;
425 }
426
427 /* figure out how long the command name is */
428 if (!isalpha(*scan))
429 {
430 cmdlen = 1;
431 }
432 else
433 {
434 for (cmdlen = 1;
435 isalpha(scan[cmdlen]);
436 cmdlen++)
437 {
438 }
439 }
440
441 /* lookup the command code */
442 for (cmdidx = 0;
443 cmdnames[cmdidx].name && strncmp(scan, cmdnames[cmdidx].name, cmdlen);
444 cmdidx++)
445 {
446 }
447 argt = cmdnames[cmdidx].argt;
448 cmd = cmdnames[cmdidx].code;
449 if (cmd == CMD_NULL)
450 {
451 msg("Unknown command \"%.*s\"", cmdlen, scan);
452 return;
453 }
454
455 /* !!! if the command doesn't have NOBAR set, then replace | with \0 */
456
457 /* if the command ended with a bang, set the forceit flag */
458 scan += cmdlen;
459 if ((argt & BANG) && *scan == '!')
460 {
461 scan++;
462 forceit = 1;
463 }
464 else
465 {
466 forceit = 0;
467 }
468
469 /* skip any more whitespace, to leave scan pointing to arguments */
470 while (isspace(*scan))
471 {
472 scan++;
473 }
474
475 /* a couple of special cases for filenames */
476 if (argt & XFILE)
477 {
478 /* if names were given, process them */
479 if (*scan)
480 {
481 for (build = tmpblk.c, iswild = didsub = FALSE; *scan; scan++)
482 {
483 switch (*scan)
484 {
485 case '\\':
486 if (scan[1] == '\\' || scan[1] == '%' || scan[1] == '#')
487 {
488 *build++ = *++scan;
489 }
490 else
491 {
492 *build++ = '\\';
493 }
494 break;
495
496 case '%':
497 if (!*origname)
498 {
499 msg("No filename to substitute for %%");
500 return;
501 }
502 strcpy(build, origname);
503 while (*build)
504 {
505 build++;
506 }
507 didsub = TRUE;
508 break;
509
510 case '#':
511 if (!*prevorig)
512 {
513 msg("No filename to substitute for #");
514 return;
515 }
516 strcpy(build, prevorig);
517 while (*build)
518 {
519 build++;
520 }
521 didsub = TRUE;
522 break;
523
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;
536
537 default:
538 *build++ = *scan;
539 }
540 }
541 *build = '\0';
542
543 if (cmd == CMD_BANG
544 || cmd == CMD_READ && tmpblk.c[0] == '!'
545 || cmd == CMD_WRITE && tmpblk.c[0] == '!')
546 {
547 if (didsub)
548 {
549 if (mode != MODE_EX)
550 {
551 addch('\n');
552 }
553 addstr(tmpblk.c);
554 addch('\n');
555 exrefresh();
556 }
557 }
558 else
559 {
560 if (iswild && tmpblk.c[0] != '>')
561 {
562 scan = wildcard(tmpblk.c);
563 }
564 }
565 }
566 else /* no names given, maybe assume origname */
567 {
568 if (!(argt & NODFL))
569 {
570 strcpy(tmpblk.c, origname);
571 }
572 else
573 {
574 *tmpblk.c = '\0';
575 }
576 }
577
578 scan = tmpblk.c;
579 }
580
581 /* bad arguments? */
582 if (!(argt & EXRCOK) && nlines < 1L)
583 {
584 msg("Can't use the \"%s\" command in a %s file", cmdnames[cmdidx].name, EXRC);
585 return;
586 }
587 if (!(argt & (ZERO | EXRCOK)) && frommark == MARK_UNSET)
588 {
589 msg("Can't use address 0 with \"%s\" command.", cmdnames[cmdidx].name);
590 return;
591 }
592 if (!(argt & FROM) && frommark != cursor && nlines >= 1L)
593 {
594 msg("Can't use address with \"%s\" command.", cmdnames[cmdidx].name);
595 return;
596 }
597 if (!(argt & TO) && tomark != frommark && nlines >= 1L)
598 {
599 msg("Can't use a range with \"%s\" command.", cmdnames[cmdidx].name);
600 return;
601 }
602 if (!(argt & EXTRA) && *scan)
603 {
604 msg("Extra characters after \"%s\" command.", cmdnames[cmdidx].name);
605 return;
606 }
607 if ((argt & NOSPC) && !(cmd == CMD_READ && (forceit || *scan == '!')))
608 {
609 build = scan;
610#ifndef CRUNCH
611 if ((argt & PLUS) && *build == '+')
612 {
613 while (*build && !isspace(*build))
614 {
615 build++;
616 }
617 while (*build && isspace(*build))
618 {
619 build++;
620 }
621 }
622#endif /* not CRUNCH */
623 for (; *build; build++)
624 {
625 if (isspace(*build))
626 {
627 msg("Too many %s to \"%s\" command.",
628 (argt & XFILE) ? "filenames" : "arguments",
629 cmdnames[cmdidx].name);
630 return;
631 }
632 }
633 }
634
635 /* some commands have special default ranges */
636 if (isdfl && (argt & DFLALL))
637 {
638 frommark = MARK_FIRST;
639 tomark = MARK_LAST;
640 }
641 else if (isdfl && (argt & DFLNONE))
642 {
643 frommark = tomark = 0L;
644 }
645
646 /* write a newline if called from visual mode */
647 if ((argt & NL) && mode != MODE_EX && !exwrote)
648 {
649 addch('\n');
650 exrefresh();
651 }
652
653 /* act on the command */
654 (*cmdnames[cmdidx].fn)(frommark, tomark, cmd, forceit, scan);
655}
656
657
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.
660 */
661int doexrc(filename)
662 char *filename; /* name of a ".exrc" file */
663{
664 int fd; /* file descriptor */
665 int len; /* length of the ".exrc" file */
666
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.
669 */
670 U_line = 0L;
671
672 /* open the file, read it, and close */
673 fd = open(filename, O_RDONLY);
674 if (fd < 0)
675 {
676 return 0;
677 }
678 len = tread(fd, U_text, BLKSIZE);
679 close(fd);
680
681 /* execute the string */
682 exstring(U_text, len, ctrl('V'));
683
684 return 1;
685}
686
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.
691 */
692void 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 */
696{
697 char single[133]; /* a single command */
698 char *src, *dest;
699 int i;
700
701 /* find & do each command */
702 for (src = buf; src < &buf[len]; src++)
703 {
704 /* Copy a single command into single[]. Convert any quoted |
705 * into a normal |, and stop at a newline or unquoted |.
706 */
707 for (dest = single, i = 0;
708 i < 132 && src < &buf[len] && *src != '\n' && *src != '|';
709 src++, i++)
710 {
711 if (src[0] == qchar && src[1] == '|')
712 {
713 src++;
714 }
715 *dest++ = *src;
716 }
717 *dest = '\0';
718
719 /* do it */
720 doexcmd(single);
721 }
722}
Note: See TracBrowser for help on using the repository browser.