1 /* cmd2.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 some of the commands - mostly ones that change text */
12 
13 #include "config.h"
14 #include "vi.h"
15 #include "regexp.h"
16 
17 
18 /*ARGSUSED*/
cmd_substitute(frommark,tomark,cmd,bang,extra)19 void cmd_substitute(frommark, tomark, cmd, bang, extra)
20 	MARK	frommark;
21 	MARK	tomark;
22 	CMD	cmd;
23 	int	bang;
24 	char	*extra;	/* rest of the command line */
25 {
26 	char	*line;	/* a line from the file */
27 	regexp	*re;	/* the compiled search expression */
28 	char	*subst;	/* the substitution string */
29 	char	*opt;	/* substitution options */
30 	int	optp;	/* boolean option: print when done? */
31 	int	optg;	/* boolean option: substitute globally in line? */
32 	long	l;	/* a line number */
33 	char	*s, *d;	/* used during subtitutions */
34 	long	chline;	/* # of lines changed */
35 	long	chsub;	/* # of substitutions made */
36 
37 
38 	/* make sure we got a search pattern */
39 	if (*extra != '/' && *extra != '?')
40 	{
41 		msg("Usage: s/regular expression/new text/");
42 		return;
43 	}
44 
45 	/* parse & compile the search pattern */
46 	subst = parseptrn(extra);
47 	re = regcomp(extra + 1);
48 	if (!re)
49 	{
50 		return;
51 	}
52 
53 	/* parse the substitution string & find the option string */
54 	for (opt = subst; *opt && (*opt != *extra || opt[-1] == '\\'); opt++)
55 	{
56 	}
57 	if (*opt)
58 	{
59 		*opt++ = '\0';
60 	}
61 
62 	/* analyse the option string */
63 	optp = optg = 0;
64 	while (*opt)
65 	{
66 		switch (*opt++)
67 		{
68 		  case 'p':	optp = 1;	break;
69 		  case 'g':	optg = 1;	break;
70 		  case ' ':
71 		  case '\t':			break;
72 		  default:
73 			msg("Subst options are p and g -- not %c", opt[-1]);
74 			return;
75 		}
76 	}
77 
78 	ChangeText
79 	{
80 		/* reset the change counters */
81 		chline = chsub = 0L;
82 
83 		/* for each selected line */
84 		for (l = markline(frommark); l <= markline(tomark); l++)
85 		{
86 			/* fetch the line */
87 			line = fetchline(l);
88 
89 			/* if it contains the search pattern... */
90 			if (regexec(re, line, TRUE))
91 			{
92 				/* increment the line change counter */
93 				chline++;
94 
95 				/* initialize the pointers */
96 				s = line;
97 				d = tmpblk.c;
98 
99 				/* do once or globally ... */
100 				do
101 				{
102 					/* increment the substitution change counter */
103 					chsub++;
104 
105 					/* this may be the first line to redraw */
106 					redrawrange(l, l + 1L, l + 1L);
107 
108 					/* copy stuff from before the match */
109 					while (s < re->startp[0])
110 					{
111 						*d++ = *s++;
112 					}
113 
114 					/* subtitute for the matched part */
115 					regsub(re, subst, d);
116 					s = re->endp[0];
117 					d += strlen(d);
118 
119 				} while (optg && regexec(re, s, FALSE));
120 
121 				/* copy stuff from after the match */
122 				while (*d++ = *s++)	/* yes, ASSIGNMENT! */
123 				{
124 				}
125 
126 				/* replace the old version of the line with the new */
127 				changeline(l, tmpblk.c);
128 
129 				/* if supposed to print it, do so */
130 				if (optp)
131 				{
132 					addstr(tmpblk.c);
133 					addch('\n');
134 					exrefresh();
135 				}
136 			}
137 		}
138 	}
139 
140 	/* report what happened */
141 	if (chsub == 0)
142 	{
143 		msg("Substitution failed");
144 	}
145 
146 	/* tweak for redrawing */
147 	if (chline > 1 || redrawafter && redrawafter != markline(cursor))
148 	{
149 		mustredraw = TRUE;
150 	}
151 
152 	/* free the regexp */
153 	free(re);
154 
155 	/* Reporting */
156 	if (chline >= *o_report)
157 	{
158 		msg("%ld substitutions on %ld lines", chsub, chline);
159 	}
160 	rptlines = 0;
161 }
162 
163 
164 
165 
166 /*ARGSUSED*/
cmd_delete(frommark,tomark,cmd,bang,extra)167 void cmd_delete(frommark, tomark, cmd, bang, extra)
168 	MARK	frommark;
169 	MARK	tomark;
170 	CMD	cmd;
171 	int	bang;
172 	char	*extra;
173 {
174 	MARK	curs2;	/* al altered form of the cursor */
175 
176 	/* choose your cut buffer */
177 	if (*extra == '"')
178 	{
179 		extra++;
180 	}
181 	if (*extra)
182 	{
183 		cutname(*extra);
184 	}
185 
186 	/* make sure we're talking about whole lines here */
187 	frommark = frommark & ~(BLKSIZE - 1);
188 	tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE;
189 
190 	/* yank the lines */
191 	cut(frommark, tomark);
192 
193 	/* if CMD_DELETE then delete the lines */
194 	if (cmd != CMD_YANK)
195 	{
196 		curs2 = cursor;
197 		ChangeText
198 		{
199 			/* delete the lines */
200 			delete(frommark, tomark);
201 		}
202 		if (curs2 > tomark)
203 		{
204 			cursor = curs2 - tomark + frommark;
205 		}
206 		else if (curs2 > frommark)
207 		{
208 			cursor = frommark;
209 		}
210 	}
211 }
212 
213 
214 /*ARGSUSED*/
cmd_append(frommark,tomark,cmd,bang,extra)215 void cmd_append(frommark, tomark, cmd, bang, extra)
216 	MARK	frommark;
217 	MARK	tomark;
218 	CMD	cmd;
219 	int	bang;
220 	char	*extra;
221 {
222 	long	l;	/* line counter */
223 
224 	ChangeText
225 	{
226 		/* if we're doing a change, delete the old version */
227 		if (cmd == CMD_CHANGE)
228 		{
229 			/* delete 'em */
230 			cmd_delete(frommark, tomark, cmd, bang, extra);
231 		}
232 
233 		/* new lines start at the frommark line, or after it */
234 		l = markline(frommark);
235 		if (cmd == CMD_APPEND)
236 		{
237  			l++;
238 		}
239 
240 		/* get lines until no more lines, or "." line, and insert them */
241 		while (vgets('\0', tmpblk.c, BLKSIZE) >= 0)
242 		{
243 			addch('\n');
244 			if (!strcmp(tmpblk.c, "."))
245 			{
246 				break;
247 			}
248 
249 			addline(l, tmpblk.c);
250 			l++;
251 		}
252 	}
253 
254 	/* on the odd chance that we're calling this from vi mode ... */
255 	redraw(MARK_UNSET, FALSE);
256 }
257 
258 
259 /*ARGSUSED*/
cmd_put(frommark,tomark,cmd,bang,extra)260 void cmd_put(frommark, tomark, cmd, bang, extra)
261 	MARK	frommark;
262 	MARK	tomark;
263 	CMD	cmd;
264 	int	bang;
265 	char	*extra;
266 {
267 	/* choose your cut buffer */
268 	if (*extra == '"')
269 	{
270 		extra++;
271 	}
272 	if (*extra)
273 	{
274 		cutname(*extra);
275 	}
276 
277 	/* paste it */
278 	ChangeText
279 	{
280 		cursor = paste(frommark, !bang, FALSE);
281 	}
282 }
283 
284 
285 /*ARGSUSED*/
cmd_join(frommark,tomark,cmd,bang,extra)286 void cmd_join(frommark, tomark, cmd, bang, extra)
287 	MARK	frommark;
288 	MARK	tomark;
289 	CMD	cmd;
290 	int	bang;
291 	char	*extra;
292 {
293 	long	l;
294 	char	*scan;
295 	int	len;	/* length of the new line */
296 
297 	/* if only one line is specified, assume the following one joins too */
298 	if (markline(frommark) == nlines)
299 	{
300 		msg("Nothing to join with this line");
301 		return;
302 	}
303 	if (markline(frommark) == markline(tomark))
304 	{
305 		tomark = m_down(tomark, 1L);
306 	}
307 
308 	/* get the first line */
309 	l = markline(frommark);
310 	strcpy(tmpblk.c, fetchline(l++));
311 	len = strlen(tmpblk.c);
312 
313 	/* build the longer line */
314 	while (l <= markline(tomark))
315 	{
316 		/* get the next line */
317 		scan = fetchline(l++);
318 
319 		/* remove any leading whitespace */
320 		while (*scan == '\t' || *scan == ' ')
321 		{
322 			scan++;
323 		}
324 
325 		/* see if the line will fit */
326 		if (strlen(scan) + len + 1 > BLKSIZE)
327 		{
328 			msg("Can't join -- the resulting line would be too long");
329 			return;
330 		}
331 
332 		/* catenate it, with a space in between */
333 		tmpblk.c[len++] = ' ';
334 		strcpy(tmpblk.c + len, scan);
335 		len += strlen(scan);
336 	}
337 
338 	/* make the change */
339 	ChangeText
340 	{
341 		frommark &= ~(BLKSIZE - 1);
342 		tomark &= ~(BLKSIZE - 1);
343 		tomark += BLKSIZE;
344 		delete(frommark, tomark);
345 		addline(markline(frommark), tmpblk.c);
346 	}
347 }
348 
349 
350 
351 /*ARGSUSED*/
cmd_shift(frommark,tomark,cmd,bang,extra)352 void cmd_shift(frommark, tomark, cmd, bang, extra)
353 	MARK	frommark;
354 	MARK	tomark;
355 	CMD	cmd;
356 	int	bang;
357 	char	*extra;
358 {
359 	long	l;	/* line number counter */
360 	int	oldidx;	/* number of chars previously used for indent */
361 	int	newidx;	/* number of chars in the new indent string */
362 	int	oldcol;	/* previous indent amount */
363 	int	newcol;	/* new indent amount */
364 	char	*text;	/* pointer to the old line's text */
365 
366 	/* figure out how much of the screen we must redraw (for vi mode) */
367 	if (markline(frommark) != markline(tomark))
368 	{
369 		mustredraw = TRUE;
370 		redrawrange(markline(frommark), markline(tomark) + 1L, markline(tomark) + 1L);
371 	}
372 
373 	ChangeText
374 	{
375 		/* for each line to shift... */
376 		for (l = markline(frommark); l <= markline(tomark); l++)
377 		{
378 			/* get the line - ignore empty lines unless ! mode */
379 			text = fetchline(l);
380 			if (!*text && !bang)
381 				continue;
382 
383 			/* calc oldidx and oldcol */
384 			for (oldidx = 0, oldcol = 0;
385 			     text[oldidx] == ' ' || text[oldidx] == '\t';
386 			     oldidx++)
387 			{
388 				if (text[oldidx] == ' ')
389 				{
390 					oldcol += 1;
391 				}
392 				else
393 				{
394 					oldcol += *o_tabstop - (oldcol % *o_tabstop);
395 				}
396 			}
397 
398 			/* calc newcol */
399 			if (cmd == CMD_SHIFTR)
400 			{
401 				newcol = oldcol + (*o_shiftwidth & 0xff);
402 			}
403 			else
404 			{
405 				newcol = oldcol - (*o_shiftwidth & 0xff);
406 				if (newcol < 0)
407 					newcol = 0;
408 			}
409 
410 			/* if no change, then skip to next line */
411 			if (oldcol == newcol)
412 				continue;
413 
414 			/* build a new indent string */
415 			newidx = 0;
416 			while (newcol >= *o_tabstop)
417 			{
418 				tmpblk.c[newidx++] = '\t';
419 				newcol -= *o_tabstop;
420 			}
421 			while (newcol > 0)
422 			{
423 				tmpblk.c[newidx++] = ' ';
424 				newcol--;
425 			}
426 			tmpblk.c[newidx] = '\0';
427 
428 			/* change the old indent string into the new */
429 			change(MARK_AT_LINE(l), MARK_AT_LINE(l) + oldidx, tmpblk.c);
430 		}
431 	}
432 
433 	/* Reporting... */
434 	rptlines = markline(tomark) - markline(frommark) + 1L;
435 	if (cmd == CMD_SHIFTR)
436 	{
437 		rptlabel = ">ed";
438 	}
439 	else
440 	{
441 		rptlabel = "<ed";
442 	}
443 }
444 
445 
446 /*ARGSUSED*/
cmd_read(frommark,tomark,cmd,bang,extra)447 void cmd_read(frommark, tomark, cmd, bang, extra)
448 	MARK	frommark;
449 	MARK	tomark;
450 	CMD	cmd;
451 	int	bang;
452 	char	*extra;
453 {
454 	long	l;	/* line number counter - where new lines go */
455 	int	fd, rc;	/* used while reading from the file */
456 	char	*scan;	/* used for finding newlines */
457 	char	*line;	/* points to the start of a line */
458 	int	prevrc;	/* used to detect abnormal EOF */
459 
460 	/* special case: if ":r !cmd" then let the filter() function do it */
461 	if (bang || extra[0] == '!')
462 	{
463 		if (extra[0] == '!')
464 		{
465 			extra++;
466 		}
467 		frommark = (frommark & ~(BLKSIZE - 1)) + BLKSIZE;
468 		filter(frommark, MARK_UNSET, extra);
469 		return;
470 	}
471 
472 	/* first line goes after the selected line */
473 	l = markline(frommark) + 1;
474 
475 	/* open the file */
476 	fd = open(extra, O_RDONLY);
477 	if (fd < 0)
478 	{
479 		msg("Can't open \"%s\"", extra);
480 		return;
481 	}
482 
483 	/* get blocks from the file, and add each line in the block */
484 	ChangeText
485 	{
486 		/* NOTE!  lint worries needlessly about the order of evaluation
487 		 * of the 'rc' expressions in the test clause of this for(;;){}
488 		 */
489 		for (prevrc = rc = 0;
490 		     (rc = rc + tread(fd, tmpblk.c + rc, BLKSIZE - rc)) > 0;
491 		     prevrc = rc)
492 		{
493 			/* if we couldn't read anything, we damn well better have \n */
494 			if (prevrc == rc)
495 			{
496 				if (rc == BLKSIZE)
497 				{
498 					rc--;
499 				}
500 				if (tmpblk.c[rc - 1] != '\n' || rc <= 0)
501 				{
502 					tmpblk.c[rc++] = '\n';
503 				}
504 			}
505 
506 			/* for each complete line in this block, add it */
507 			for (line = scan = tmpblk.c; rc > 0; rc--, scan++)
508 			{
509 				if (*scan == '\n')
510 				{
511 					*scan = '\0';
512 					addline(l, line);
513 					l++;
514 					line = scan + 1;
515 				}
516 				else if (!*scan)
517 				{
518 					/* protect against NUL chars in file */
519 					*scan = 0x7f;
520 				}
521 			}
522 
523 			/* any extra chars are shifted to the start of the buffer */
524 			rc = scan - line;
525 			for (scan = tmpblk.c; scan < tmpblk.c + rc; )
526 			{
527 				*scan++ = *line++;
528 			}
529 		}
530 	}
531 
532 	/* close the file */
533 	close(fd);
534 
535 	/* Reporting... */
536 	rptlines = l - markline(frommark) - 1L;
537 	rptlabel = "read";
538 }
539 
540 
541 /*ARGSUSED*/
cmd_list(frommark,tomark,cmd,bang,extra)542 void cmd_list(frommark, tomark, cmd, bang, extra)
543 	MARK	frommark;
544 	MARK	tomark;
545 	CMD	cmd;
546 	int	bang;
547 	char	*extra;
548 {
549 	long		l;	/* line number counter */
550 	register char	*scan;	/* used for moving through the line */
551 
552 	for (l = markline(frommark); l <= markline(tomark); l++)
553 	{
554 		/* list the line */
555 		scan = fetchline(l);
556 
557 		while (*scan)
558 		{
559 			/* if the char is non-printable, write it as \000 */
560 			if (*scan < ' ' || *scan > '~')
561 			{
562 				/* build the \000 form & write it */
563 				addch('\\');
564 				addch('0' + ((*scan >> 6) & 3));
565 				addch('0' + ((*scan >> 3) & 7));
566 				addch('0' + (*scan & 7));
567 			}
568 			else
569 			{
570 				addch(*scan);
571 			}
572 			scan++;
573 		}
574 
575 		/* write a $ and a \n */
576 		addstr("$\n");
577 		exrefresh();
578 	}
579 }
580 
581 
582 /*ARGSUSED*/
cmd_undo(frommark,tomark,cmd,bang,extra)583 void cmd_undo(frommark, tomark, cmd, bang, extra)
584 	MARK	frommark;
585 	MARK	tomark;
586 	CMD	cmd;
587 	int	bang;
588 	char	*extra;
589 {
590 	undo();
591 }
592 
593 
594 /* print the selected lines */
595 /*ARGSUSED*/
cmd_print(frommark,tomark,cmd,bang,extra)596 void cmd_print(frommark, tomark, cmd, bang, extra)
597 	MARK	frommark;
598 	MARK	tomark;
599 	CMD	cmd;
600 	int	bang;
601 	char	*extra;
602 {
603 	register char	*scan;
604 	register long	l;
605 	register int	col;
606 
607 	for (l = markline(frommark); l <= markline(tomark); l++)
608 	{
609 		/* get the next line & display it */
610 		for (col = 0, scan = fetchline(l); *scan; scan++)
611 		{
612 			/* expand tabs to the proper width */
613 			if (*scan == '\t')
614 			{
615 				do
616 				{
617 					qaddch(' ');
618 					col++;
619 				} while (col % *o_tabstop != 0);
620 			}
621 			else
622 			{
623 				qaddch(*scan);
624 			}
625 
626 			/* wrap at the edge of the screen */
627 			if (!has_AM && col >= COLS)
628 			{
629 				addch('\n');
630 				col = 0;
631 			}
632 		}
633 		addch('\n');
634 		exrefresh();
635 	}
636 }
637 
638 
639 /* move or copy selected lines */
640 /*ARGSUSED*/
cmd_move(frommark,tomark,cmd,bang,extra)641 void cmd_move(frommark, tomark, cmd, bang, extra)
642 	MARK	frommark;
643 	MARK	tomark;
644 	CMD	cmd;
645 	int	bang;
646 	char	*extra;
647 {
648 	MARK	destmark;
649 
650 	/* parse the destination linespec.  No defaults.  Line 0 is okay */
651 	destmark = cursor;
652 	if (!strcmp(extra, "0"))
653 	{
654 		destmark = 0L;
655 	}
656 	else if (linespec(extra, &destmark) == extra || !destmark)
657 	{
658 		msg("invalid destination address");
659 		return;
660 	}
661 
662 	/* flesh the marks out to encompass whole lines */
663 	frommark &= ~(BLKSIZE - 1);
664 	tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE;
665 	destmark = (destmark & ~(BLKSIZE - 1)) + BLKSIZE;
666 
667 	/* make sure the destination is valid */
668 	if (cmd == CMD_MOVE && destmark >= frommark && destmark < tomark)
669 	{
670 		msg("invalid destination address");
671 	}
672 
673 	/* Do it */
674 	ChangeText
675 	{
676 		/* save the text to a cut buffer */
677 		cutname('\0');
678 		cut(frommark, tomark);
679 
680 		/* if we're not copying, delete the old text & adjust destmark */
681 		if (cmd != CMD_COPY)
682 		{
683 			delete(frommark, tomark);
684 			if (destmark >= frommark)
685 			{
686 				destmark -= (tomark - frommark);
687 			}
688 		}
689 
690 		/* add the new text */
691 		paste(destmark, FALSE, FALSE);
692 	}
693 
694 	/* move the cursor to the last line of the moved text */
695 	cursor = destmark + (tomark - frommark) - BLKSIZE;
696 	if (cursor < MARK_FIRST || cursor >= MARK_LAST + BLKSIZE)
697 	{
698 		cursor = MARK_LAST;
699 	}
700 
701 	/* Reporting... */
702 	rptlabel = ( (cmd == CMD_COPY) ? "copied" : "moved" );
703 }
704 
705 
706 
707 /* execute EX commands from a file */
708 /*ARGSUSED*/
cmd_source(frommark,tomark,cmd,bang,extra)709 void cmd_source(frommark, tomark, cmd, bang, extra)
710 	MARK	frommark;
711 	MARK	tomark;
712 	CMD	cmd;
713 	int	bang;
714 	char	*extra;
715 {
716 	/* must have a filename */
717 	if (!*extra)
718 	{
719 		msg("\"source\" requires a filename");
720 		return;
721 	}
722 
723 	doexrc(extra);
724 }
725