|
|
1.1 root 1: /* ex.c */
2:
3: /* Author:
4: * Steve Kirkendall
5: * 14407 SW Teal Blvd. #C
6: * Beaverton, OR 97005
7: * [email protected]
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 */
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 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: */
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+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: */
155: char *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 */
180: char *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. */
291: void 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:
342: void 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: scan++;
388: frommark = tomark = (*scan ? MARK_UNSET : MARK_FIRST);
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: */
661: int 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: #ifdef CRUNCH
668: /* small address space - we need to conserve space */
669:
670: /* !!! kludge: we use U_text as the buffer. This has the side-effect
671: * of interfering with the shift-U visual command. Disable shift-U.
672: */
673: U_line = 0L;
674: #else
675: # if TINYSTACK
676: /* small stack - we need to conserve space */
677:
678: /* !!! kludge: we use U_text as the buffer. This has the side-effect
679: * of interfering with the shift-U visual command. Disable shift-U.
680: */
681: U_line = 0L;
682: # else
683: /* This is how we would *like* to do it -- with a large buffer on the
684: * stack, so we can handle large .exrc files and also recursion.
685: */
686: char U_text[4096];
687: # endif
688: #endif
689:
690: /* open the file, read it, and close */
691: fd = open(filename, O_RDONLY);
692: if (fd < 0)
693: {
694: return 0;
695: }
696: len = tread(fd, U_text, sizeof U_text);
697: close(fd);
698:
699: /* execute the string */
700: exstring(U_text, len, ctrl('V'));
701:
702: return 1;
703: }
704:
705: /* This function executes EX commands from a string. The commands may be
706: * separated by newlines or by | characters. It also handles quoting.
707: * Each individual command is limited to 132 bytes, but the total string
708: * may be longer.
709: */
710: void exstring(buf, len, qchar)
711: char *buf; /* the commands to execute */
712: int len; /* the length of the string */
713: int qchar; /* the quote character -- ^V for file, or \ for kbd */
714: {
715: char single[133]; /* a single command */
716: char *src, *dest;
717: int i;
718:
719: /* find & do each command */
720: for (src = buf; src < &buf[len]; src++)
721: {
722: /* Copy a single command into single[]. Convert any quoted |
723: * into a normal |, and stop at a newline or unquoted |.
724: */
725: for (dest = single, i = 0;
726: i < 132 && src < &buf[len] && *src != '\n' && *src != '|';
727: src++, i++)
728: {
729: if (src[0] == qchar && src[1] == '|')
730: {
731: src++;
732: }
733: *dest++ = *src;
734: }
735: *dest = '\0';
736:
737: /* do it */
738: doexcmd(single);
739: }
740: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.