replace undo semantics, use '.' to repeat undo/redo op
[nvi.git] / vi / v_itxt.c
blob979c13ab95acc1d0eb369c8fd9ed756b4db49de3
1 /*-
2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
6 */
8 #ifndef lint
9 static char sccsid[] = "$Id: v_itxt.c,v 8.20 1993/12/09 19:43:19 bostic Exp $ (Berkeley) $Date: 1993/12/09 19:43:19 $";
10 #endif /* not lint */
12 #include <sys/types.h>
14 #include <ctype.h>
15 #include <errno.h>
16 #include <stdlib.h>
17 #include <string.h>
19 #include "vi.h"
20 #include "vcmd.h"
23 * Repeated input in the historic vi is mostly wrong and this isn't very
24 * backward compatible. For example, if the user entered "3Aab\ncd" in
25 * the historic vi, the "ab" was repeated 3 times, and the "\ncd" was then
26 * appended to the result. There was also a hack which I don't remember
27 * right now, where "3o" would open 3 lines and then let the user fill them
28 * in, to make screen movements on 300 baud modems more tolerable. I don't
29 * think it's going to be missed.
32 #define SET_TXT_STD(sp, f) { \
33 LF_INIT((f) | TXT_BEAUTIFY | TXT_CNTRLT | TXT_ESCAPE | \
34 TXT_MAPINPUT | TXT_RECORD | TXT_RESOLVE); \
35 if (O_ISSET(sp, O_ALTWERASE)) \
36 LF_SET(TXT_ALTWERASE); \
37 if (O_ISSET(sp, O_AUTOINDENT)) \
38 LF_SET(TXT_AUTOINDENT); \
39 if (O_ISSET(sp, O_SHOWMATCH)) \
40 LF_SET(TXT_SHOWMATCH); \
41 if (O_ISSET(sp, O_WRAPMARGIN)) \
42 LF_SET(TXT_WRAPMARGIN); \
43 if (F_ISSET(sp, S_SCRIPT)) \
44 LF_SET(TXT_CR); \
45 if (O_ISSET(sp, O_TTYWERASE)) \
46 LF_SET(TXT_TTYWERASE); \
49 static int v_CS __P((SCR *, EXF *, VICMDARG *, MARK *, MARK *, MARK *, u_int));
52 * v_iA -- [count]A
53 * Append text to the end of the line.
55 int
56 v_iA(sp, ep, vp, fm, tm, rp)
57 SCR *sp;
58 EXF *ep;
59 VICMDARG *vp;
60 MARK *fm, *tm, *rp;
62 recno_t lno;
63 u_long cnt;
64 size_t len;
65 u_int flags;
66 char *p;
68 SET_TXT_STD(sp, TXT_APPENDEOL);
69 if (F_ISSET(vp, VC_ISDOT))
70 LF_SET(TXT_REPLAY);
71 lno = fm->lno;
72 for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
73 /* Move the cursor to the end of the line + 1. */
74 if ((p = file_gline(sp, ep, lno, &len)) == NULL) {
75 if (file_lline(sp, ep, &lno))
76 return (1);
77 if (lno != 0) {
78 GETLINE_ERR(sp, lno);
79 return (1);
81 lno = 1;
82 len = 0;
83 } else
84 sp->cno = len;
86 if (v_ntext(sp, ep,
87 &sp->tiq, NULL, p, len, rp, 0, OOBLNO, flags))
88 return (1);
90 SET_TXT_STD(sp, TXT_APPENDEOL | TXT_REPLAY);
91 sp->lno = lno = rp->lno;
92 sp->cno = rp->cno;
94 return (0);
98 * v_ia -- [count]a
99 * Append text to the cursor position.
102 v_ia(sp, ep, vp, fm, tm, rp)
103 SCR *sp;
104 EXF *ep;
105 VICMDARG *vp;
106 MARK *fm, *tm, *rp;
108 recno_t lno;
109 u_long cnt;
110 u_int flags;
111 size_t len;
112 char *p;
114 SET_TXT_STD(sp, 0);
115 if (F_ISSET(vp, VC_ISDOT))
116 LF_SET(TXT_REPLAY);
117 lno = fm->lno;
118 for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
120 * Move the cursor one column to the right and
121 * repaint the screen.
123 if ((p = file_gline(sp, ep, lno, &len)) == NULL) {
124 if (file_lline(sp, ep, &lno))
125 return (1);
126 if (lno != 0) {
127 GETLINE_ERR(sp, lno);
128 return (1);
130 lno = 1;
131 len = 0;
132 LF_SET(TXT_APPENDEOL);
133 } else if (len) {
134 if (len == sp->cno + 1) {
135 sp->cno = len;
136 LF_SET(TXT_APPENDEOL);
137 } else
138 ++sp->cno;
139 } else
140 LF_SET(TXT_APPENDEOL);
142 if (v_ntext(sp, ep,
143 &sp->tiq, NULL, p, len, rp, 0, OOBLNO, flags))
144 return (1);
146 SET_TXT_STD(sp, TXT_REPLAY);
147 sp->lno = lno = rp->lno;
148 sp->cno = rp->cno;
150 return (0);
154 * v_iI -- [count]I
155 * Insert text at the first non-blank character in the line.
158 v_iI(sp, ep, vp, fm, tm, rp)
159 SCR *sp;
160 EXF *ep;
161 VICMDARG *vp;
162 MARK *fm, *tm, *rp;
164 recno_t lno;
165 u_long cnt;
166 size_t len, wlen;
167 u_int flags;
168 char *p, *t;
170 SET_TXT_STD(sp, 0);
171 if (F_ISSET(vp, VC_ISDOT))
172 LF_SET(TXT_REPLAY);
173 lno = fm->lno;
174 for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
176 * Move the cursor to the start of the line and repaint
177 * the screen.
179 if ((p = file_gline(sp, ep, lno, &len)) == NULL) {
180 if (file_lline(sp, ep, &lno))
181 return (1);
182 if (lno != 0) {
183 GETLINE_ERR(sp, lno);
184 return (1);
186 lno = 1;
187 len = 0;
188 } else {
189 for (t = p, wlen = len; wlen-- && isblank(*t); ++t);
190 sp->cno = t - p;
192 if (len == 0)
193 LF_SET(TXT_APPENDEOL);
195 if (v_ntext(sp, ep,
196 &sp->tiq, NULL, p, len, rp, 0, OOBLNO, flags))
197 return (1);
199 SET_TXT_STD(sp, TXT_REPLAY);
200 sp->lno = lno = rp->lno;
201 sp->cno = rp->cno;
203 return (0);
207 * v_ii -- [count]i
208 * Insert text at the cursor position.
211 v_ii(sp, ep, vp, fm, tm, rp)
212 SCR *sp;
213 EXF *ep;
214 VICMDARG *vp;
215 MARK *fm, *tm, *rp;
217 recno_t lno;
218 u_long cnt;
219 size_t len;
220 u_int flags;
221 char *p;
223 SET_TXT_STD(sp, 0);
224 if (F_ISSET(vp, VC_ISDOT))
225 LF_SET(TXT_REPLAY);
226 lno = fm->lno;
227 for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
228 if ((p = file_gline(sp, ep, lno, &len)) == NULL) {
229 if (file_lline(sp, ep, &lno))
230 return (1);
231 if (lno != 0) {
232 GETLINE_ERR(sp, fm->lno);
233 return (1);
235 lno = 1;
236 len = 0;
238 /* If len == sp->cno, it's a replay caused by a count. */
239 if (len == 0 || len == sp->cno)
240 LF_SET(TXT_APPENDEOL);
242 if (v_ntext(sp, ep,
243 &sp->tiq, NULL, p, len, rp, 0, OOBLNO, flags))
244 return (1);
247 * On replay, if the line isn't empty, advance the insert
248 * by one (make it an append).
250 SET_TXT_STD(sp, TXT_REPLAY);
251 sp->lno = lno = rp->lno;
252 if ((sp->cno = rp->cno) != 0)
253 ++sp->cno;
255 return (0);
259 * v_iO -- [count]O
260 * Insert text above this line.
263 v_iO(sp, ep, vp, fm, tm, rp)
264 SCR *sp;
265 EXF *ep;
266 VICMDARG *vp;
267 MARK *fm, *tm, *rp;
269 recno_t ai_line, lno;
270 size_t len;
271 u_long cnt;
272 u_int flags;
273 char *p;
275 SET_TXT_STD(sp, TXT_APPENDEOL);
276 if (F_ISSET(vp, VC_ISDOT))
277 LF_SET(TXT_REPLAY);
278 for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
279 if (sp->lno == 1) {
280 if (file_lline(sp, ep, &lno))
281 return (1);
282 if (lno != 0)
283 goto insert;
284 p = NULL;
285 len = 0;
286 ai_line = OOBLNO;
287 } else {
288 insert: p = "";
289 if (file_iline(sp, ep, sp->lno, p, 0))
290 return (1);
291 if ((p = file_gline(sp, ep, sp->lno, &len)) == NULL) {
292 GETLINE_ERR(sp, sp->lno);
293 return (1);
295 sp->cno = 0;
296 ai_line = sp->lno + 1;
299 if (v_ntext(sp, ep,
300 &sp->tiq, NULL, p, len, rp, 0, ai_line, flags))
301 return (1);
303 SET_TXT_STD(sp, TXT_APPENDEOL | TXT_REPLAY);
304 sp->lno = lno = rp->lno;
305 sp->cno = rp->cno;
307 return (0);
311 * v_io -- [count]o
312 * Insert text after this line.
315 v_io(sp, ep, vp, fm, tm, rp)
316 SCR *sp;
317 EXF *ep;
318 VICMDARG *vp;
319 MARK *fm, *tm, *rp;
321 recno_t ai_line, lno;
322 size_t len;
323 u_long cnt;
324 u_int flags;
325 char *p;
327 SET_TXT_STD(sp, TXT_APPENDEOL);
328 if (F_ISSET(vp, VC_ISDOT))
329 LF_SET(TXT_REPLAY);
330 for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) {
331 if (sp->lno == 1) {
332 if (file_lline(sp, ep, &lno))
333 return (1);
334 if (lno != 0)
335 goto insert;
336 p = NULL;
337 len = 0;
338 ai_line = OOBLNO;
339 } else {
340 insert: p = "";
341 len = 0;
342 if (file_aline(sp, ep, 1, sp->lno, p, len))
343 return (1);
344 if ((p = file_gline(sp, ep, ++sp->lno, &len)) == NULL) {
345 GETLINE_ERR(sp, sp->lno);
346 return (1);
348 sp->cno = 0;
349 ai_line = sp->lno - 1;
352 if (v_ntext(sp, ep,
353 &sp->tiq, NULL, p, len, rp, 0, ai_line, flags))
354 return (1);
356 SET_TXT_STD(sp, TXT_APPENDEOL | TXT_REPLAY);
357 sp->lno = lno = rp->lno;
358 sp->cno = rp->cno;
360 return (0);
364 * v_Change -- [buffer][count]C
365 * Change line command.
368 v_Change(sp, ep, vp, fm, tm, rp)
369 SCR *sp;
370 EXF *ep;
371 VICMDARG *vp;
372 MARK *fm, *tm, *rp;
374 return (v_CS(sp, ep, vp, fm, tm, rp, 0));
378 * v_Subst -- [buffer][count]S
379 * Line substitute command.
382 v_Subst(sp, ep, vp, fm, tm, rp)
383 SCR *sp;
384 EXF *ep;
385 VICMDARG *vp;
386 MARK *fm, *tm, *rp;
388 u_int flags;
391 * The S command is the same as a 'C' command from the beginning
392 * of the line. This is hard to do in the parser, so do it here.
394 * If autoindent is on, the change is from the first *non-blank*
395 * character of the line, not the first character. And, to make
396 * it just a bit more exciting, the initial space is handled as
397 * auto-indent characters.
399 LF_INIT(0);
400 if (O_ISSET(sp, O_AUTOINDENT)) {
401 fm->cno = 0;
402 if (nonblank(sp, ep, fm->lno, &fm->cno))
403 return (1);
404 LF_SET(TXT_AICHARS);
405 } else
406 fm->cno = 0;
407 sp->cno = fm->cno;
408 return (v_CS(sp, ep, vp, fm, tm, rp, flags));
412 * v_CS --
413 * C and S commands.
415 static int
416 v_CS(sp, ep, vp, fm, tm, rp, iflags)
417 SCR *sp;
418 EXF *ep;
419 VICMDARG *vp;
420 MARK *fm, *tm, *rp;
421 u_int iflags;
423 recno_t lno;
424 size_t len;
425 char *p;
426 u_int flags;
428 SET_TXT_STD(sp, iflags);
429 if (F_ISSET(vp, VC_ISDOT))
430 LF_SET(TXT_REPLAY);
433 * There are two cases -- if a count is supplied, we do a line
434 * mode change where we delete the lines and then insert text
435 * into a new line. Otherwise, we replace the current line.
437 tm->lno = fm->lno + (F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0);
438 if (fm->lno != tm->lno) {
439 /* Make sure that the to line is real. */
440 if (file_gline(sp, ep, tm->lno, NULL) == NULL) {
441 v_eof(sp, ep, fm);
442 return (1);
445 /* Cut the lines. */
446 if (cut(sp, ep,
447 F_ISSET(vp, VC_BUFFER) ? vp->buffer : DEFCB, fm, tm, 1))
448 return (1);
450 /* Insert a line while we still can... */
451 if (file_iline(sp, ep, fm->lno, "", 0))
452 return (1);
453 ++fm->lno;
454 ++tm->lno;
456 /* Delete the lines. */
457 if (delete(sp, ep, fm, tm, 1))
458 return (1);
460 /* Get the inserted line. */
461 if ((p = file_gline(sp, ep, --fm->lno, &len)) == NULL) {
462 GETLINE_ERR(sp, fm->lno);
463 return (1);
465 tm = NULL;
466 sp->lno = fm->lno;
467 sp->cno = 0;
468 LF_SET(TXT_APPENDEOL);
469 } else {
470 /* The line may be empty, but that's okay. */
471 if ((p = file_gline(sp, ep, fm->lno, &len)) == NULL) {
472 if (file_lline(sp, ep, &lno))
473 return (1);
474 if (lno != 0) {
475 GETLINE_ERR(sp, tm->lno);
476 return (1);
478 len = 0;
479 LF_SET(TXT_APPENDEOL);
480 } else {
481 if (cut(sp, ep,
482 F_ISSET(vp, VC_BUFFER) ? vp->buffer : DEFCB,
483 fm, tm, 1))
484 return (1);
485 tm->cno = len;
486 if (len == 0)
487 LF_SET(TXT_APPENDEOL);
488 LF_SET(TXT_EMARK | TXT_OVERWRITE);
491 return (v_ntext(sp, ep,
492 &sp->tiq, tm, p, len, rp, 0, OOBLNO, flags));
496 * v_change -- [buffer][count]c[count]motion
497 * Change command.
500 v_change(sp, ep, vp, fm, tm, rp)
501 SCR *sp;
502 EXF *ep;
503 VICMDARG *vp;
504 MARK *fm, *tm, *rp;
506 recno_t lno;
507 size_t blen, len;
508 u_int flags;
509 int lmode, rval;
510 char *bp, *p;
512 SET_TXT_STD(sp, 0);
513 if (F_ISSET(vp, VC_ISDOT))
514 LF_SET(TXT_REPLAY);
517 * Move the cursor to the start of the change. Note, if autoindent
518 * is turned on, the cc command in line mode changes from the first
519 * *non-blank* character of the line, not the first character. And,
520 * to make it just a bit more exciting, the initial space is handled
521 * as auto-indent characters.
523 if (lmode = F_ISSET(vp, VC_LMODE)) {
524 fm->cno = 0;
525 if (O_ISSET(sp, O_AUTOINDENT)) {
526 if (nonblank(sp, ep, fm->lno, &fm->cno))
527 return (1);
528 LF_SET(TXT_AICHARS);
531 sp->lno = fm->lno;
532 sp->cno = fm->cno;
535 * If changing within a single line, the line either currently has
536 * text or it doesn't. If it doesn't, just insert text. Otherwise,
537 * copy it and overwrite it.
539 if (fm->lno == tm->lno) {
540 if ((p = file_gline(sp, ep, fm->lno, &len)) == NULL) {
541 if (p == NULL) {
542 if (file_lline(sp, ep, &lno))
543 return (1);
544 if (lno != 0) {
545 GETLINE_ERR(sp, fm->lno);
546 return (1);
549 len = 0;
550 LF_SET(TXT_APPENDEOL);
551 } else {
552 if (cut(sp, ep,
553 F_ISSET(vp, VC_BUFFER) ? vp->buffer : DEFCB,
554 fm, tm, lmode))
555 return (1);
556 if (len == 0)
557 LF_SET(TXT_APPENDEOL);
558 LF_SET(TXT_EMARK | TXT_OVERWRITE);
560 return (v_ntext(sp, ep,
561 &sp->tiq, tm, p, len, rp, 0, OOBLNO, flags));
565 * It's trickier if changing over multiple lines. If we're in
566 * line mode we delete all of the lines and insert a replacement
567 * line which the user edits. If there was leading whitespace
568 * in the first line being changed, we copy it and use it as the
569 * replacement. If we're not in line mode, we just delete the
570 * text and start inserting.
572 * Copy the text.
574 if (cut(sp, ep,
575 F_ISSET(vp, VC_BUFFER) ? vp->buffer : DEFCB, fm, tm, lmode))
576 return (1);
578 /* If replacing entire lines and there's leading text. */
579 if (lmode && fm->cno) {
580 /* Get a copy of the first line changed. */
581 if ((p = file_gline(sp, ep, fm->lno, &len)) == NULL) {
582 GETLINE_ERR(sp, fm->lno);
583 return (1);
585 /* Copy the leading text elsewhere. */
586 GET_SPACE_RET(sp, bp, blen, fm->cno);
587 memmove(bp, p, fm->cno);
588 } else
589 bp = NULL;
591 /* Delete the text. */
592 if (delete(sp, ep, fm, tm, lmode))
593 return (1);
595 /* If replacing entire lines, insert a replacement line. */
596 if (lmode) {
597 if (file_iline(sp, ep, fm->lno, bp, fm->cno))
598 return (1);
599 sp->lno = fm->lno;
600 len = sp->cno = fm->cno;
603 /* Get the line we're editing. */
604 if ((p = file_gline(sp, ep, fm->lno, &len)) == NULL) {
605 if (file_lline(sp, ep, &lno))
606 return (1);
607 if (lno != 0) {
608 GETLINE_ERR(sp, fm->lno);
609 return (1);
611 len = 0;
614 /* Check to see if we're appending to the line. */
615 if (fm->cno >= len)
616 LF_SET(TXT_APPENDEOL);
618 /* No to mark. */
619 tm = NULL;
621 rval = v_ntext(sp, ep, &sp->tiq, tm, p, len, rp, 0, OOBLNO, flags);
623 if (bp != NULL)
624 FREE_SPACE(sp, bp, blen);
625 return (rval);
629 * v_Replace -- [count]R
630 * Overwrite multiple characters.
633 v_Replace(sp, ep, vp, fm, tm, rp)
634 SCR *sp;
635 EXF *ep;
636 VICMDARG *vp;
637 MARK *fm, *tm, *rp;
639 recno_t lno;
640 u_long cnt;
641 size_t len;
642 u_int flags;
643 char *p;
645 SET_TXT_STD(sp, 0);
646 if (F_ISSET(vp, VC_ISDOT))
647 LF_SET(TXT_REPLAY);
649 cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
650 if ((p = file_gline(sp, ep, rp->lno, &len)) == NULL) {
651 if (file_lline(sp, ep, &lno))
652 return (1);
653 if (lno != 0) {
654 GETLINE_ERR(sp, rp->lno);
655 return (1);
657 len = 0;
658 LF_SET(TXT_APPENDEOL);
659 } else {
660 if (len == 0)
661 LF_SET(TXT_APPENDEOL);
662 LF_SET(TXT_OVERWRITE | TXT_REPLACE);
664 tm->lno = rp->lno;
665 tm->cno = len ? len : 0;
666 if (v_ntext(sp, ep, &sp->tiq, tm, p, len, rp, 0, OOBLNO, flags))
667 return (1);
670 * Special case. The historic vi handled [count]R badly, in that R
671 * would replace some number of characters, and then the count would
672 * append count-1 copies of the replacing chars to the replaced space.
673 * This seems wrong, so this version counts R commands. There is some
674 * trickiness in moving back to where the user stopped replacing after
675 * each R command. Basically, if the user ended with a newline, we
676 * want to use rp->cno (which will be 0). Otherwise, use the column
677 * after the returned cursor, unless it would be past the end of the
678 * line, in which case we append to the line.
680 while (--cnt) {
681 if ((p = file_gline(sp, ep, rp->lno, &len)) == NULL)
682 GETLINE_ERR(sp, rp->lno);
683 SET_TXT_STD(sp, TXT_REPLAY);
685 sp->lno = rp->lno;
687 if (len == 0 || rp->cno == len - 1) {
688 sp->cno = len;
689 LF_SET(TXT_APPENDEOL);
690 } else {
691 sp->cno = rp->cno;
692 if (rp->cno != 0)
693 ++sp->cno;
694 LF_SET(TXT_OVERWRITE | TXT_REPLACE);
697 if (v_ntext(sp, ep,
698 &sp->tiq, tm, p, len, rp, 0, OOBLNO, flags))
699 return (1);
701 return (0);
705 * v_subst -- [buffer][count]s
706 * Substitute characters.
709 v_subst(sp, ep, vp, fm, tm, rp)
710 SCR *sp;
711 EXF *ep;
712 VICMDARG *vp;
713 MARK *fm, *tm, *rp;
715 recno_t lno;
716 size_t len;
717 u_int flags;
718 char *p;
720 SET_TXT_STD(sp, 0);
721 if (F_ISSET(vp, VC_ISDOT))
722 LF_SET(TXT_REPLAY);
723 if ((p = file_gline(sp, ep, fm->lno, &len)) == NULL) {
724 if (file_lline(sp, ep, &lno))
725 return (1);
726 if (lno != 0) {
727 GETLINE_ERR(sp, fm->lno);
728 return (1);
730 len = 0;
731 LF_SET(TXT_APPENDEOL);
732 } else {
733 if (len == 0)
734 LF_SET(TXT_APPENDEOL);
735 LF_SET(TXT_EMARK | TXT_OVERWRITE);
738 tm->lno = fm->lno;
739 tm->cno = fm->cno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1);
740 if (tm->cno > len)
741 tm->cno = len;
743 if (p != NULL && cut(sp, ep,
744 F_ISSET(vp, VC_BUFFER) ? vp->buffer : DEFCB, fm, tm, 0))
745 return (1);
747 return (v_ntext(sp, ep,
748 &sp->tiq, tm, p, len, rp, 0, OOBLNO, flags));