|
|
1.1 root 1: /* ex.c */
2:
3: /* Author:
4: * Steve Kirkendall
5: * 16820 SW Tallac Way
6: * Beaverton, OR 97006
7: * [email protected], 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 NL 1024 /* if mode!=MODE_EX, then write a newline first */
34: #define PLUS 2048 /* allow a line number, as in ":e +32 foo" */
35: #define ZERO 4096 /* allow 0 to be given as a line number */
36: #define FILES (XFILE + EXTRA) /* multiple extra files allowed */
37: #define WORD1 (EXTRA + NOSPC) /* one extra word allowed */
38: #define FILE1 (FILES + NOSPC) /* 1 file allowed, defaults to current file */
39: #define NAMEDF (FILE1 + NODFL) /* 1 file allowed, defaults to "" */
40: #define NAMEDFS (FILES + NODFL) /* multiple files allowed, default is "" */
41: #define RANGE (FROM + TO) /* range of linespecs allowed */
42: #define NONE 0 /* no args allowed at all */
43:
44: /* This array maps ex command names to command codes. The order in which
45: * command names are listed below is significant -- ambiguous abbreviations
46: * are always resolved to be the first possible match. (e.g. "r" is taken
47: * to mean "read", not "rewind", because "read" comes before "rewind")
48: */
49: static struct
50: {
51: char *name; /* name of the command */
52: CMD code; /* enum code of the command */
53: void (*fn)();/* function which executes the command */
54: ARGT argt; /* command line arguments permitted/needed/used */
55: }
56: cmdnames[] =
57: { /* cmd name cmd code function arguments */
58: {"append", CMD_APPEND, cmd_append, FROM+ZERO },
59: #ifdef DEBUG
60: {"bug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA+NL},
61: #endif
62: {"change", CMD_CHANGE, cmd_append, RANGE },
63: {"delete", CMD_DELETE, cmd_delete, RANGE+WORD1 },
64: {"edit", CMD_EDIT, cmd_edit, BANG+FILE1+PLUS },
65: {"file", CMD_FILE, cmd_file, NAMEDF },
66: {"global", CMD_GLOBAL, cmd_global, RANGE+BANG+EXTRA+DFLALL},
67: {"insert", CMD_INSERT, cmd_append, FROM },
68: {"join", CMD_INSERT, cmd_join, RANGE },
69: {"k", CMD_MARK, cmd_mark, FROM+WORD1 },
70: {"list", CMD_LIST, cmd_print, RANGE+NL },
71: {"move", CMD_MOVE, cmd_move, RANGE+EXTRA },
72: {"next", CMD_NEXT, cmd_next, BANG+NAMEDFS },
73: {"Next", CMD_PREVIOUS, cmd_next, BANG },
74: {"print", CMD_PRINT, cmd_print, RANGE+NL },
75: {"quit", CMD_QUIT, cmd_xit, BANG },
76: {"read", CMD_READ, cmd_read, FROM+ZERO+NAMEDF},
77: {"substitute", CMD_SUBSTITUTE, cmd_substitute, RANGE+EXTRA },
78: {"to", CMD_COPY, cmd_move, RANGE+EXTRA },
79: {"undo", CMD_UNDO, cmd_undo, NONE },
80: {"vglobal", CMD_VGLOBAL, cmd_global, RANGE+EXTRA+DFLALL},
81: {"write", CMD_WRITE, cmd_write, RANGE+BANG+FILE1+DFLALL},
82: {"xit", CMD_XIT, cmd_xit, BANG+NL },
83: {"yank", CMD_YANK, cmd_delete, RANGE+WORD1 },
84:
85: {"!", CMD_BANG, cmd_shell, EXRCOK+RANGE+NAMEDFS+DFLNONE+NL},
86: {"<", CMD_SHIFTL, cmd_shift, RANGE },
87: {">", CMD_SHIFTR, cmd_shift, RANGE },
88: {"=", CMD_EQUAL, cmd_file, RANGE },
89: {"&", CMD_SUBAGAIN, cmd_substitute, RANGE },
90: #ifndef NO_AT
91: {"@", CMD_AT, cmd_at, EXTRA },
92: #endif
93:
94: #ifndef NO_ABBR
95: {"abbreviate", CMD_ABBR, cmd_abbr, EXRCOK+EXTRA },
96: #endif
97: {"args", CMD_ARGS, cmd_args, EXRCOK+NAMEDFS },
98: #ifndef NO_ERRLIST
99: {"cc", CMD_CC, cmd_make, BANG+FILES },
100: #endif
101: {"cd", CMD_CD, cmd_cd, EXRCOK+NAMEDF },
102: {"copy", CMD_COPY, cmd_move, RANGE+EXTRA },
103: #ifndef NO_DIGRAPH
104: {"digraph", CMD_DIGRAPH, cmd_digraph, EXRCOK+BANG+EXTRA},
105: #endif
106: #ifndef NO_ERRLIST
107: {"errlist", CMD_ERRLIST, cmd_errlist, BANG+NAMEDF },
108: #endif
109: {"ex", CMD_EDIT, cmd_edit, BANG+FILE1 },
110: {"map", CMD_MAP, cmd_map, EXRCOK+BANG+EXTRA},
111: #ifndef NO_MKEXRC
112: {"mkexrc", CMD_MKEXRC, cmd_mkexrc, NAMEDF },
113: #endif
114: {"number", CMD_NUMBER, cmd_print, RANGE+NL },
115: {"put", CMD_PUT, cmd_put, FROM+ZERO+WORD1 },
116: {"set", CMD_SET, cmd_set, EXRCOK+EXTRA },
117: {"shell", CMD_SHELL, cmd_shell, NL },
118: {"source", CMD_SOURCE, cmd_source, EXRCOK+NAMEDF },
119: {"tag", CMD_TAG, cmd_tag, BANG+WORD1 },
120: {"version", CMD_VERSION, cmd_version, EXRCOK+NONE },
121: {"visual", CMD_VISUAL, cmd_visual, NONE },
122: {"wq", CMD_WQUIT, cmd_xit, NL },
123:
124: #ifdef DEBUG
125: {"debug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA+NL},
126: {"validate", CMD_VALIDATE, cmd_validate, BANG+NL },
127: #endif
128: {"chdir", CMD_CD, cmd_cd, EXRCOK+NAMEDF },
129: #ifndef NO_ERRLIST
130: {"make", CMD_MAKE, cmd_make, BANG+NAMEDFS },
131: #endif
132: {"mark", CMD_MARK, cmd_mark, FROM+WORD1 },
133: {"previous", CMD_PREVIOUS, cmd_next, BANG },
134: {"rewind", CMD_REWIND, cmd_next, BANG },
135: {"unmap", CMD_UNMAP, cmd_map, EXRCOK+BANG+EXTRA},
136: #ifndef NO_ABBR
137: {"unabbreviate",CMD_UNABBR, cmd_abbr, EXRCOK+WORD1 },
138: #endif
139:
140: {(char *)0}
141: };
142:
143:
144: /* This function parses a search pattern - given a pointer to a / or ?,
145: * it replaces the ending / or ? with a \0, and returns a pointer to the
146: * stuff that came after the pattern.
147: */
148: char *parseptrn(ptrn)
149: REG char *ptrn;
150: {
151: REG char *scan;
152:
153: for (scan = ptrn + 1;
154: *scan && *scan != *ptrn;
155: scan++)
156: {
157: /* allow backslashed versions of / and ? in the pattern */
158: if (*scan == '\\' && scan[1] != '\0')
159: {
160: scan++;
161: }
162: }
163: if (*scan)
164: {
165: *scan++ = '\0';
166: }
167:
168: return scan;
169: }
170:
171:
172: /* This function parses a line specifier for ex commands */
173: char *linespec(s, markptr)
174: REG char *s; /* start of the line specifier */
175: MARK *markptr; /* where to store the mark's value */
176: {
177: long num;
178: REG char *t;
179:
180: /* parse each ;-delimited clause of this linespec */
181: do
182: {
183: /* skip an initial ';', if any */
184: if (*s == ';')
185: {
186: s++;
187: }
188:
189: /* skip leading spaces */
190: while (isascii(*s) && isspace(*s))
191: {
192: s++;
193: }
194:
195: /* dot means current position */
196: if (*s == '.')
197: {
198: s++;
199: *markptr = cursor;
200: }
201: /* '$' means the last line */
202: else if (*s == '$')
203: {
204: s++;
205: *markptr = MARK_LAST;
206: }
207: /* digit means an absolute line number */
208: else if (isascii(*s) && isdigit(*s))
209: {
210: for (num = 0; isascii(*s) && isdigit(*s); s++)
211: {
212: num = num * 10 + *s - '0';
213: }
214: *markptr = MARK_AT_LINE(num);
215: }
216: /* appostrophe means go to a set mark */
217: else if (*s == '\'')
218: {
219: s++;
220: *markptr = m_tomark(cursor, 1L, (int)*s);
221: s++;
222: }
223: /* slash means do a search */
224: else if (*s == '/' || *s == '?')
225: {
226: /* put a '\0' at the end of the search pattern */
227: t = parseptrn(s);
228:
229: /* search for the pattern */
230: *markptr &= ~(BLKSIZE - 1);
231: if (*s == '/')
232: {
233: pfetch(markline(*markptr));
234: if (plen > 0)
235: *markptr += plen - 1;
236: *markptr = m_fsrch(*markptr, s);
237: }
238: else
239: {
240: *markptr = m_bsrch(*markptr, s);
241: }
242:
243: /* adjust command string pointer */
244: s = t;
245: }
246:
247: /* if linespec was faulty, quit now */
248: if (!*markptr)
249: {
250: return s;
251: }
252:
253: /* maybe add an offset */
254: t = s;
255: if (*t == '-' || *t == '+')
256: {
257: s++;
258: for (num = 0; *s >= '0' && *s <= '9'; s++)
259: {
260: num = num * 10 + *s - '0';
261: }
262: if (num == 0)
263: {
264: num = 1;
265: }
266: *markptr = m_updnto(*markptr, num, *t);
267: }
268: } while (*s == ';' || *s == '+' || *s == '-');
269:
270: return s;
271: }
272:
273:
274:
275: /* This function reads an ex command and executes it. */
276: void ex()
277: {
278: char cmdbuf[80];
279: REG int cmdlen;
280: static long oldline;
281:
282: significant = FALSE;
283: oldline = markline(cursor);
284:
285: while (mode == MODE_EX)
286: {
287: /* read a line */
288: cmdlen = vgets(':', cmdbuf, sizeof cmdbuf);
289: if (cmdlen < 0)
290: {
291: return;
292: }
293:
294: /* if empty line, assume ".+1" */
295: if (cmdlen == 0)
296: {
297: strcpy(cmdbuf, ".+1");
298: qaddch('\r');
299: clrtoeol();
300: }
301: else
302: {
303: addch('\n');
304: }
305: refresh();
306:
307: /* parse & execute the command */
308: doexcmd(cmdbuf);
309:
310: /* handle autoprint */
311: if (significant || markline(cursor) != oldline)
312: {
313: significant = FALSE;
314: oldline = markline(cursor);
315: if (*o_autoprint && mode == MODE_EX)
316: {
317: cmd_print(cursor, cursor, CMD_PRINT, FALSE, "");
318: }
319: }
320: }
321: }
322:
323: void doexcmd(cmdbuf)
324: char *cmdbuf; /* string containing an ex command */
325: {
326: REG char *scan; /* used to scan thru cmdbuf */
327: MARK frommark; /* first linespec */
328: MARK tomark; /* second linespec */
329: REG int cmdlen; /* length of the command name given */
330: CMD cmd; /* what command is this? */
331: ARGT argt; /* argument types for this command */
332: short forceit; /* bang version of a command? */
333: REG int cmdidx; /* index of command */
334: REG char *build; /* used while copying filenames */
335: int iswild; /* boolean: filenames use wildcards? */
336: int isdfl; /* using default line ranges? */
337: int didsub; /* did we substitute file names for % or # */
338:
339:
340: /* ex commands can't be undone via the shift-U command */
341: U_line = 0L;
342:
343: /* ignore command lines that start with a double-quote */
344: if (*cmdbuf == '"')
345: {
346: return;
347: }
348:
349: /* permit extra colons at the start of the line */
350: while (*cmdbuf == ':')
351: {
352: cmdbuf++;
353: }
354:
355: /* parse the line specifier */
356: scan = cmdbuf;
357: if (nlines < 1)
358: {
359: /* no file, so don't allow addresses */
360: }
361: else if (*scan == '%')
362: {
363: /* '%' means all lines */
364: frommark = MARK_FIRST;
365: tomark = MARK_LAST;
366: scan++;
367: }
368: else if (*scan == '0')
369: {
370: frommark = tomark = MARK_UNSET;
371: scan++;
372: }
373: else
374: {
375: frommark = cursor;
376: scan = linespec(scan, &frommark);
377: tomark = frommark;
378: if (frommark && *scan == ',')
379: {
380: scan++;
381: scan = linespec(scan, &tomark);
382: }
383: if (!tomark)
384: {
385: /* faulty line spec -- fault already described */
386: return;
387: }
388: if (frommark > tomark)
389: {
390: msg("first address exceeds the second");
391: return;
392: }
393: }
394: isdfl = (scan == cmdbuf);
395:
396: /* skip whitespace */
397: while (isascii(*scan) && isspace(*scan))
398: {
399: scan++;
400: }
401:
402: /* if no command, then just move the cursor to the mark */
403: if (!*scan)
404: {
405: cursor = tomark;
406: return;
407: }
408:
409: /* figure out how long the command name is */
410: if (isascii(*scan) && !isalpha(*scan))
411: {
412: cmdlen = 1;
413: }
414: else
415: {
416: for (cmdlen = 1;
417: !isascii(scan[cmdlen]) || isalpha(scan[cmdlen]);
418: cmdlen++)
419: {
420: }
421: }
422:
423: /* lookup the command code */
424: for (cmdidx = 0;
425: cmdnames[cmdidx].name && strncmp(scan, cmdnames[cmdidx].name, cmdlen);
426: cmdidx++)
427: {
428: }
429: argt = cmdnames[cmdidx].argt;
430: cmd = cmdnames[cmdidx].code;
431: if (cmd == CMD_NULL)
432: {
433: #if OSK
434: msg("Unknown command \"%s\"", scan);
435: #else
436: msg("Unknown command \"%.*s\"", cmdlen, scan);
437: #endif
438: return;
439: }
440:
441: /* if the command ended with a bang, set the forceit flag */
442: scan += cmdlen;
443: if ((argt & BANG) && *scan == '!')
444: {
445: scan++;
446: forceit = 1;
447: }
448: else
449: {
450: forceit = 0;
451: }
452:
453: /* skip any more whitespace, to leave scan pointing to arguments */
454: while (isascii(*scan) && isspace(*scan))
455: {
456: scan++;
457: }
458:
459: /* a couple of special cases for filenames */
460: if (argt & XFILE)
461: {
462: /* if names were given, process them */
463: if (*scan)
464: {
465: for (build = tmpblk.c, iswild = didsub = FALSE; *scan; scan++)
466: {
467: switch (*scan)
468: {
469: case '%':
470: if (!*origname)
471: {
472: msg("No filename to substitute for %%");
473: return;
474: }
475: strcpy(build, origname);
476: while (*build)
477: {
478: build++;
479: }
480: didsub = TRUE;
481: break;
482:
483: case '#':
484: if (!*prevorig)
485: {
486: msg("No filename to substitute for #");
487: return;
488: }
489: strcpy(build, prevorig);
490: while (*build)
491: {
492: build++;
493: }
494: didsub = TRUE;
495: break;
496:
497: case '*':
498: case '?':
499: #if !(MSDOS || TOS)
500: case '[':
501: case '`':
502: case '{': /* } */
503: case '$':
504: case '~':
505: #endif
506: *build++ = *scan;
507: iswild = TRUE;
508: break;
509:
510: default:
511: *build++ = *scan;
512: }
513: }
514: *build = '\0';
515:
516: if (cmd == CMD_BANG
517: || cmd == CMD_READ && tmpblk.c[0] == '!'
518: || cmd == CMD_WRITE && tmpblk.c[0] == '!')
519: {
520: if (didsub)
521: {
522: if (mode != MODE_EX)
523: {
524: addch('\n');
525: }
526: addstr(tmpblk.c);
527: addch('\n');
528: exrefresh();
529: }
530: }
531: else
532: {
533: if (iswild && tmpblk.c[0] != '>')
534: {
535: scan = wildcard(tmpblk.c);
536: }
537: }
538: }
539: else /* no names given, maybe assume origname */
540: {
541: if (!(argt & NODFL))
542: {
543: strcpy(tmpblk.c, origname);
544: }
545: else
546: {
547: *tmpblk.c = '\0';
548: }
549: }
550:
551: scan = tmpblk.c;
552: }
553:
554: /* bad arguments? */
555: if (!(argt & EXRCOK) && nlines < 1L)
556: {
557: msg("Can't use the \"%s\" command in a %s file", cmdnames[cmdidx].name, EXRC);
558: return;
559: }
560: if (!(argt & (ZERO | EXRCOK)) && frommark == MARK_UNSET)
561: {
562: msg("Can't use address 0 with \"%s\" command.", cmdnames[cmdidx].name);
563: return;
564: }
565: if (!(argt & FROM) && frommark != cursor && nlines >= 1L)
566: {
567: msg("Can't use address with \"%s\" command.", cmdnames[cmdidx].name);
568: return;
569: }
570: if (!(argt & TO) && tomark != frommark && nlines >= 1L)
571: {
572: msg("Can't use a range with \"%s\" command.", cmdnames[cmdidx].name);
573: return;
574: }
575: if (!(argt & EXTRA) && *scan)
576: {
577: msg("Extra characters after \"%s\" command.", cmdnames[cmdidx].name);
578: return;
579: }
580: if ((argt & NOSPC) && !(cmd == CMD_READ && (forceit || *scan == '!')))
581: {
582: build = scan;
583: #ifndef CRUNCH
584: if ((argt & PLUS) && *build == '+')
585: {
586: while (*build && !(isascii(*build) && isspace(*build)))
587: {
588: build++;
589: }
590: while (*build && isascii(*build) && isspace(*build))
591: {
592: build++;
593: }
594: }
595: #endif /* not CRUNCH */
596: for (; *build; build++)
597: {
598: if (isspace(*build))
599: {
600: msg("Too many %s to \"%s\" command.",
601: (argt & XFILE) ? "filenames" : "arguments",
602: cmdnames[cmdidx].name);
603: return;
604: }
605: }
606: }
607:
608: /* some commands have special default ranges */
609: if (isdfl && (argt & DFLALL))
610: {
611: frommark = MARK_FIRST;
612: tomark = MARK_LAST;
613: }
614: else if (isdfl && (argt & DFLNONE))
615: {
616: frommark = tomark = 0L;
617: }
618:
619: /* write a newline if called from visual mode */
620: if ((argt & NL) && mode != MODE_EX && !exwrote)
621: {
622: addch('\n');
623: exrefresh();
624: }
625:
626: /* act on the command */
627: (*cmdnames[cmdidx].fn)(frommark, tomark, cmd, forceit, scan);
628: }
629:
630:
631: /* This function executes EX commands from a file. It returns 1 normally, or
632: * 0 if the file could not be opened for reading.
633: */
634: int doexrc(filename)
635: char *filename; /* name of a ".exrc" file */
636: {
637: int fd; /* file descriptor */
638: int len; /* length of the ".exrc" file */
639: char buf[MAXRCLEN]; /* buffer, holds the entire .exrc file */
640:
641: /* open the file, read it, and close */
642: fd = open(filename, O_RDONLY);
643: if (fd < 0)
644: {
645: return 0;
646: }
647: len = tread(fd, buf, MAXRCLEN);
648: close(fd);
649:
650: /* execute the string */
651: exstring(buf, len);
652:
653: return 1;
654: }
655:
656: void exstring(buf, len)
657: char *buf; /* the commands to execute */
658: int len; /* the length of the string */
659: {
660: char *cmd; /* start of a command */
661: char *end; /* used to search for the end of cmd */
662:
663: /* find & do each command */
664: for (cmd = buf; cmd < &buf[len]; cmd = end + 1)
665: {
666: /* find the end of the command */
667: for (end = cmd; *end != '\n' && *end != '|'; end++)
668: {
669: }
670: *end = '\0';
671:
672: /* do it */
673: doexcmd(cmd);
674: }
675: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.