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