|
|
1.1 root 1: /* cmd2.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 some of the commands - mostly ones that change text */
12:
13: #include <ctype.h>
14: #include "config.h"
15: #include "vi.h"
16: #include "regexp.h"
17: #if TOS
18: # include <stat.h>
19: #else
20: # if OSK
21: # include "osk.h"
22: # else
23: # include <sys/stat.h>
24: # endif
25: #endif
26:
27:
28: /*ARGSUSED*/
29: void cmd_substitute(frommark, tomark, cmd, bang, extra)
30: MARK frommark;
31: MARK tomark;
32: CMD cmd;
33: int bang;
34: char *extra; /* rest of the command line */
35: {
36: char *line; /* a line from the file */
37: regexp *re; /* the compiled search expression */
38: char *subst; /* the substitution string */
39: char *opt; /* substitution options */
40: long l; /* a line number */
41: char *s, *d; /* used during subtitutions */
42: char *conf; /* used during confirmation */
43: long chline; /* # of lines changed */
44: long chsub; /* # of substitutions made */
45: static optp; /* boolean option: print when done? */
46: static optg; /* boolean option: substitute globally in line? */
47: static optc; /* boolean option: confirm before subst? */
48:
49:
50: /* for now, assume this will fail */
51: rptlines = -1L;
52:
53: if (cmd == CMD_SUBAGAIN)
54: {
55: #ifndef NO_MAGIC
56: if (*o_magic)
57: subst = "~";
58: else
59: #endif
60: subst = "\\~";
61: re = regcomp("");
62:
63: /* if visual "&", then turn off the "p" and "c" options */
64: if (bang)
65: {
66: optp = optc = FALSE;
67: }
68: }
69: else
70: {
71: /* make sure we got a search pattern */
72: if (*extra != '/' && *extra != '?')
73: {
74: msg("Usage: s/regular expression/new text/");
75: return;
76: }
77:
78: /* parse & compile the search pattern */
79: subst = parseptrn(extra);
80: re = regcomp(extra + 1);
81: }
82:
83: /* abort if RE error -- error message already given by regcomp() */
84: if (!re)
85: {
86: return;
87: }
88:
89: if (cmd == CMD_SUBSTITUTE)
90: {
91: /* parse the substitution string & find the option string */
92: for (opt = subst; *opt && *opt != *extra; opt++)
93: {
94: if (*opt == '\\' && opt[1])
95: {
96: opt++;
97: }
98: }
99: if (*opt)
100: {
101: *opt++ = '\0';
102: }
103:
104: /* analyse the option string */
105: if (!*o_edcompatible)
106: {
107: optp = optg = optc = FALSE;
108: }
109: while (*opt)
110: {
111: switch (*opt++)
112: {
113: case 'p': optp = !optp; break;
114: case 'g': optg = !optg; break;
115: case 'c': optc = !optc; break;
116: case ' ':
117: case '\t': break;
118: default:
119: msg("Subst options are p, c, and g -- not %c", opt[-1]);
120: return;
121: }
122: }
123: }
124:
125: /* if "c" or "p" flag was given, and we're in visual mode, then NEWLINE */
126: if ((optc || optp) && mode == MODE_VI)
127: {
128: addch('\n');
129: exrefresh();
130: }
131:
132: ChangeText
133: {
134: /* reset the change counters */
135: chline = chsub = 0L;
136:
137: /* for each selected line */
138: for (l = markline(frommark); l <= markline(tomark); l++)
139: {
140: /* fetch the line */
141: line = fetchline(l);
142:
143: /* if it contains the search pattern... */
144: if (regexec(re, line, TRUE))
145: {
146: /* increment the line change counter */
147: chline++;
148:
149: /* initialize the pointers */
150: s = line;
151: d = tmpblk.c;
152:
153: /* do once or globally ... */
154: do
155: {
156: #ifndef CRUNCH
157: /* confirm, if necessary */
158: if (optc)
159: {
160: for (conf = line; conf < re->startp[0]; conf++)
161: addch(*conf);
162: standout();
163: for ( ; conf < re->endp[0]; conf++)
164: addch(*conf);
165: standend();
166: for (; *conf; conf++)
167: addch(*conf);
168: addch('\n');
169: exrefresh();
170: if (getkey(0) != 'y')
171: {
172: /* copy accross the original chars */
173: while (s < re->endp[0])
174: *d++ = *s++;
175:
176: /* skip to next match on this line, if any */
177: continue;
178: }
179: }
180: #endif /* not CRUNCH */
181:
182: /* increment the substitution change counter */
183: chsub++;
184:
185: /* this may be the first line to redraw */
186: redrawrange(l, l + 1L, l + 1L);
187:
188: /* copy stuff from before the match */
189: while (s < re->startp[0])
190: {
191: *d++ = *s++;
192: }
193:
194: /* substitute for the matched part */
195: regsub(re, subst, d);
196: s = re->endp[0];
197: d += strlen(d);
198:
199: } while (optg && regexec(re, s, FALSE));
200:
201: /* copy stuff from after the match */
202: while (*d++ = *s++) /* yes, ASSIGNMENT! */
203: {
204: }
205:
206: /* replace the old version of the line with the new */
207: d[-1] = '\n';
208: d[0] = '\0';
209: change(MARK_AT_LINE(l), MARK_AT_LINE(l + 1), tmpblk.c);
210:
211: /* if supposed to print it, do so */
212: if (optp)
213: {
214: addstr(tmpblk.c);
215: exrefresh();
216: }
217:
218: /* move the cursor to that line */
219: cursor = MARK_AT_LINE(l);
220: }
221: }
222: }
223:
224: /* tweak for redrawing */
225: mustredraw = TRUE;
226:
227: /* free the regexp */
228: free(re);
229:
230: /* if done from within a ":g" command, then finish silently */
231: if (doingglobal)
232: {
233: rptlines = chline;
234: rptlabel = "changed";
235: return;
236: }
237:
238: /* Reporting */
239: if (chsub == 0)
240: {
241: msg("Substitution failed");
242: }
243: else if (chline >= *o_report)
244: {
245: msg("%ld substitutions on %ld lines", chsub, chline);
246: }
247: }
248:
249:
250:
251:
252: /*ARGSUSED*/
253: void cmd_delete(frommark, tomark, cmd, bang, extra)
254: MARK frommark;
255: MARK tomark;
256: CMD cmd;
257: int bang;
258: char *extra;
259: {
260: MARK curs2; /* an altered form of the cursor */
261:
262: /* choose your cut buffer */
263: if (*extra == '"')
264: {
265: extra++;
266: }
267: if (*extra)
268: {
269: cutname(*extra);
270: }
271:
272: /* make sure we're talking about whole lines here */
273: frommark = frommark & ~(BLKSIZE - 1);
274: tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE;
275:
276: /* yank the lines */
277: cut(frommark, tomark);
278:
279: /* if CMD_DELETE then delete the lines */
280: if (cmd != CMD_YANK)
281: {
282: curs2 = cursor;
283: ChangeText
284: {
285: /* delete the lines */
286: delete(frommark, tomark);
287: }
288: if (curs2 > tomark)
289: {
290: cursor = curs2 - tomark + frommark;
291: }
292: else if (curs2 > frommark)
293: {
294: cursor = frommark;
295: }
296: }
297: }
298:
299:
300: /*ARGSUSED*/
301: void cmd_append(frommark, tomark, cmd, bang, extra)
302: MARK frommark;
303: MARK tomark;
304: CMD cmd;
305: int bang;
306: char *extra;
307: {
308: long l; /* line counter */
309:
310: ChangeText
311: {
312: /* if we're doing a change, delete the old version */
313: if (cmd == CMD_CHANGE)
314: {
315: /* delete 'em */
316: cmd_delete(frommark, tomark, cmd, bang, extra);
317: }
318:
319: /* new lines start at the frommark line, or after it */
320: l = markline(frommark);
321: if (cmd == CMD_APPEND)
322: {
323: l++;
324: }
325:
326: /* get lines until no more lines, or "." line, and insert them */
327: while (vgets('\0', tmpblk.c, BLKSIZE) >= 0)
328: {
329: addch('\n');
330: if (!strcmp(tmpblk.c, "."))
331: {
332: break;
333: }
334:
335: strcat(tmpblk.c, "\n");
336: add(MARK_AT_LINE(l), tmpblk.c);
337: l++;
338: }
339: }
340:
341: /* on the odd chance that we're calling this from vi mode ... */
342: redraw(MARK_UNSET, FALSE);
343: }
344:
345:
346: /*ARGSUSED*/
347: void cmd_put(frommark, tomark, cmd, bang, extra)
348: MARK frommark;
349: MARK tomark;
350: CMD cmd;
351: int bang;
352: char *extra;
353: {
354: /* choose your cut buffer */
355: if (*extra == '"')
356: {
357: extra++;
358: }
359: if (*extra)
360: {
361: cutname(*extra);
362: }
363:
364: /* paste it */
365: ChangeText
366: {
367: cursor = paste(frommark, TRUE, FALSE);
368: }
369: }
370:
371:
372: /*ARGSUSED*/
373: void cmd_join(frommark, tomark, cmd, bang, extra)
374: MARK frommark;
375: MARK tomark;
376: CMD cmd;
377: int bang;
378: char *extra;
379: {
380: long l;
381: char *scan;
382: int len; /* length of the new line */
383:
384: /* if only one line is specified, assume the following one joins too */
385: if (markline(frommark) == nlines)
386: {
387: msg("Nothing to join with this line");
388: return;
389: }
390: if (markline(frommark) == markline(tomark))
391: {
392: tomark += BLKSIZE;
393: }
394:
395: /* get the first line */
396: l = markline(frommark);
397: strcpy(tmpblk.c, fetchline(l));
398: len = strlen(tmpblk.c);
399:
400: /* build the longer line */
401: while (++l <= markline(tomark))
402: {
403: /* get the next line */
404: scan = fetchline(l);
405:
406: /* remove any leading whitespace */
407: while (*scan == '\t' || *scan == ' ')
408: {
409: scan++;
410: }
411:
412: /* see if the line will fit */
413: if (strlen(scan) + len + 3 > BLKSIZE)
414: {
415: msg("Can't join -- the resulting line would be too long");
416: return;
417: }
418:
419: /* catenate it, with a space (or two) in between */
420: if (len >= 1 &&
421: (tmpblk.c[len - 1] == '.'
422: || tmpblk.c[len - 1] == '?'
423: || tmpblk.c[len - 1] == '!'))
424: {
425: tmpblk.c[len++] = ' ';
426: }
427: tmpblk.c[len++] = ' ';
428: strcpy(tmpblk.c + len, scan);
429: len += strlen(scan);
430: }
431: tmpblk.c[len++] = '\n';
432: tmpblk.c[len] = '\0';
433:
434: /* make the change */
435: ChangeText
436: {
437: frommark &= ~(BLKSIZE - 1);
438: tomark &= ~(BLKSIZE - 1);
439: tomark += BLKSIZE;
440: change(frommark, tomark, tmpblk.c);
441: }
442:
443: /* Reporting... */
444: rptlines = markline(tomark) - markline(frommark) - 1L;
445: rptlabel = "joined";
446: }
447:
448:
449:
450: /*ARGSUSED*/
451: void cmd_shift(frommark, tomark, cmd, bang, extra)
452: MARK frommark;
453: MARK tomark;
454: CMD cmd;
455: int bang;
456: char *extra;
457: {
458: long l; /* line number counter */
459: int oldidx; /* number of chars previously used for indent */
460: int newidx; /* number of chars in the new indent string */
461: int oldcol; /* previous indent amount */
462: int newcol; /* new indent amount */
463: char *text; /* pointer to the old line's text */
464:
465: /* figure out how much of the screen we must redraw (for vi mode) */
466: if (markline(frommark) != markline(tomark))
467: {
468: mustredraw = TRUE;
469: redrawrange(markline(frommark), markline(tomark) + 1L, markline(tomark) + 1L);
470: }
471:
472: ChangeText
473: {
474: /* for each line to shift... */
475: for (l = markline(frommark); l <= markline(tomark); l++)
476: {
477: /* get the line - ignore empty lines unless ! mode */
478: text = fetchline(l);
479: if (!*text && !bang)
480: continue;
481:
482: /* calc oldidx and oldcol */
483: for (oldidx = 0, oldcol = 0;
484: text[oldidx] == ' ' || text[oldidx] == '\t';
485: oldidx++)
486: {
487: if (text[oldidx] == ' ')
488: {
489: oldcol += 1;
490: }
491: else
492: {
493: oldcol += *o_tabstop - (oldcol % *o_tabstop);
494: }
495: }
496:
497: /* calc newcol */
498: if (cmd == CMD_SHIFTR)
499: {
500: newcol = oldcol + (*o_shiftwidth & 0xff);
501: }
502: else
503: {
504: newcol = oldcol - (*o_shiftwidth & 0xff);
505: if (newcol < 0)
506: newcol = 0;
507: }
508:
509: /* if no change, then skip to next line */
510: if (oldcol == newcol)
511: continue;
512:
513: /* build a new indent string */
514: newidx = 0;
515: while (newcol >= *o_tabstop)
516: {
517: tmpblk.c[newidx++] = '\t';
518: newcol -= *o_tabstop;
519: }
520: while (newcol > 0)
521: {
522: tmpblk.c[newidx++] = ' ';
523: newcol--;
524: }
525: tmpblk.c[newidx] = '\0';
526:
527: /* change the old indent string into the new */
528: change(MARK_AT_LINE(l), MARK_AT_LINE(l) + oldidx, tmpblk.c);
529: }
530: }
531:
532: /* Reporting... */
533: rptlines = markline(tomark) - markline(frommark) + 1L;
534: if (cmd == CMD_SHIFTR)
535: {
536: rptlabel = ">ed";
537: }
538: else
539: {
540: rptlabel = "<ed";
541: }
542: }
543:
544:
545: /*ARGSUSED*/
546: void cmd_read(frommark, tomark, cmd, bang, extra)
547: MARK frommark;
548: MARK tomark;
549: CMD cmd;
550: int bang;
551: char *extra;
552: {
553: int fd, rc; /* used while reading from the file */
554: char *scan; /* used for finding NUL characters */
555: int hadnul; /* boolean: any NULs found? */
556: int addnl; /* boolean: forced to add newlines? */
557: int len; /* number of chars in current line */
558: long lines; /* number of lines in current block */
559: struct stat statb;
560:
561: /* special case: if ":r !cmd" then let the filter() function do it */
562: if (extra[0] == '!')
563: {
564: filter(frommark, MARK_UNSET, extra + 1);
565: return;
566: }
567:
568: /* open the file */
569: fd = open(extra, O_RDONLY);
570: if (fd < 0)
571: {
572: msg("Can't open \"%s\"", extra);
573: return;
574: }
575:
576: #ifndef CRUNCH
577: if (stat(extra, &statb) < 0)
578: {
579: msg("Can't stat \"%s\"", extra);
580: }
581: # if TOS
582: if (statb.st_mode & S_IJDIR)
583: # else
584: # if OSK
585: if (statb.st_mode & S_IFDIR)
586: # else
587: if ((statb.st_mode & S_IFMT) != S_IFREG)
588: # endif
589: # endif
590: {
591: msg("\"%s\" is not a regular file", extra);
592: return;
593: }
594: #endif /* not CRUNCH */
595:
596: /* get blocks from the file, and add them */
597: ChangeText
598: {
599: /* insertion starts at the line following frommark */
600: tomark = frommark = (frommark | (BLKSIZE - 1L)) + 1L;
601: len = 0;
602: hadnul = addnl = FALSE;
603:
604: /* add an extra newline, so partial lines at the end of
605: * the file don't trip us up
606: */
607: add(tomark, "\n");
608:
609: /* for each chunk of text... */
610: while ((rc = read(fd, tmpblk.c, BLKSIZE - 1)) > 0)
611: {
612: /* count newlines, convert NULs, etc. ... */
613: for (lines = 0, scan = tmpblk.c; rc > 0; rc--, scan++)
614: {
615: /* break up long lines */
616: if (*scan != '\n' && len + 2 > BLKSIZE)
617: {
618: *scan = '\n';
619: addnl = TRUE;
620: }
621:
622: /* protect against NUL chars in file */
623: if (!*scan)
624: {
625: *scan = 0x80;
626: hadnul = TRUE;
627: }
628:
629: /* starting a new line? */
630: if (*scan == '\n')
631: {
632: /* reset length at newline */
633: len = 0;
634: lines++;
635: }
636: else
637: {
638: len++;
639: }
640: }
641:
642: /* add the text */
643: *scan = '\0';
644: add(tomark, tmpblk.c);
645: tomark += MARK_AT_LINE(lines) + len - markidx(tomark);
646: }
647:
648: /* if partial last line, then retain that first newline */
649: if (len > 0)
650: {
651: msg("Last line had no newline");
652: tomark += BLKSIZE; /* <- for the rptlines calc */
653: }
654: else /* delete that first newline */
655: {
656: delete(tomark, (tomark | (BLKSIZE - 1L)) + 1L);
657: }
658: }
659:
660: /* close the file */
661: close(fd);
662:
663: /* Reporting... */
664: rptlines = markline(tomark) - markline(frommark);
665: rptlabel = "read";
666:
667: if (addnl)
668: msg("Newlines were added to break up long lines");
669: if (hadnul)
670: msg("NULs were converted to 0x80");
671: }
672:
673:
674:
675: /*ARGSUSED*/
676: void cmd_undo(frommark, tomark, cmd, bang, extra)
677: MARK frommark;
678: MARK tomark;
679: CMD cmd;
680: int bang;
681: char *extra;
682: {
683: undo();
684: }
685:
686:
687: /* print the selected lines */
688: /*ARGSUSED*/
689: void cmd_print(frommark, tomark, cmd, bang, extra)
690: MARK frommark;
691: MARK tomark;
692: CMD cmd;
693: int bang;
694: char *extra;
695: {
696: REG char *scan;
697: REG long l;
698: REG int col;
699:
700: for (l = markline(frommark); l <= markline(tomark); l++)
701: {
702: /* display a line number, if CMD_NUMBER */
703: if (cmd == CMD_NUMBER)
704: {
705: sprintf(tmpblk.c, "%6ld ", l);
706: qaddstr(tmpblk.c);
707: col = 8;
708: }
709: else
710: {
711: col = 0;
712: }
713:
714: /* get the next line & display it */
715: for (scan = fetchline(l); *scan; scan++)
716: {
717: /* expand tabs to the proper width */
718: if (*scan == '\t' && cmd != CMD_LIST)
719: {
720: do
721: {
722: qaddch(' ');
723: col++;
724: } while (col % *o_tabstop != 0);
725: }
726: else if (*scan >= 0 && *scan < ' ' || *scan == '\177')
727: {
728: qaddch('^');
729: qaddch(*scan ^ 0x40);
730: col += 2;
731: }
732: else if ((*scan & 0x80) && cmd == CMD_LIST)
733: {
734: sprintf(tmpblk.c, "\\%03o", *scan);
735: qaddstr(tmpblk.c);
736: col += 4;
737: }
738: else
739: {
740: qaddch(*scan);
741: col++;
742: }
743:
744: /* wrap at the edge of the screen */
745: if (!has_AM && col >= COLS)
746: {
747: addch('\n');
748: col -= COLS;
749: }
750: }
751: if (cmd == CMD_LIST)
752: {
753: qaddch('$');
754: }
755: addch('\n');
756: exrefresh();
757: }
758: }
759:
760:
761: /* move or copy selected lines */
762: /*ARGSUSED*/
763: void cmd_move(frommark, tomark, cmd, bang, extra)
764: MARK frommark;
765: MARK tomark;
766: CMD cmd;
767: int bang;
768: char *extra;
769: {
770: MARK destmark;
771:
772: /* parse the destination linespec. No defaults. Line 0 is okay */
773: destmark = cursor;
774: if (!strcmp(extra, "0"))
775: {
776: destmark = 0L;
777: }
778: else if (linespec(extra, &destmark) == extra || !destmark)
779: {
780: msg("invalid destination address");
781: return;
782: }
783:
784: /* flesh the marks out to encompass whole lines */
785: frommark &= ~(BLKSIZE - 1);
786: tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE;
787: destmark = (destmark & ~(BLKSIZE - 1)) + BLKSIZE;
788:
789: /* make sure the destination is valid */
790: if (cmd == CMD_MOVE && destmark >= frommark && destmark < tomark)
791: {
792: msg("invalid destination address");
793: }
794:
795: /* Do it */
796: ChangeText
797: {
798: /* save the text to a cut buffer */
799: cutname('\0');
800: cut(frommark, tomark);
801:
802: /* if we're not copying, delete the old text & adjust destmark */
803: if (cmd != CMD_COPY)
804: {
805: delete(frommark, tomark);
806: if (destmark >= frommark)
807: {
808: destmark -= (tomark - frommark);
809: }
810: }
811:
812: /* add the new text */
813: paste(destmark, FALSE, FALSE);
814: }
815:
816: /* move the cursor to the last line of the moved text */
817: cursor = destmark + (tomark - frommark) - BLKSIZE;
818: if (cursor < MARK_FIRST || cursor >= MARK_LAST + BLKSIZE)
819: {
820: cursor = MARK_LAST;
821: }
822:
823: /* Reporting... */
824: rptlabel = ( (cmd == CMD_COPY) ? "copied" : "moved" );
825: }
826:
827:
828:
829: /* execute EX commands from a file */
830: /*ARGSUSED*/
831: void cmd_source(frommark, tomark, cmd, bang, extra)
832: MARK frommark;
833: MARK tomark;
834: CMD cmd;
835: int bang;
836: char *extra;
837: {
838: /* must have a filename */
839: if (!*extra)
840: {
841: msg("\"source\" requires a filename");
842: return;
843: }
844:
845: doexrc(extra);
846: }
847:
848:
849: #ifndef NO_AT
850: /*ARGSUSED*/
851: void cmd_at(frommark, tomark, cmd, bang, extra)
852: MARK frommark;
853: MARK tomark;
854: CMD cmd;
855: int bang;
856: char *extra;
857: {
858: static nest = FALSE;
859: int result;
860: char buf[MAXRCLEN];
861:
862: /* don't allow nested macros */
863: if (nest)
864: {
865: msg("@ macros can't be nested");
866: return;
867: }
868: nest = TRUE;
869:
870: /* require a buffer name */
871: if (*extra == '"')
872: extra++;
873: if (!*extra || !isascii(*extra) ||!islower(*extra))
874: {
875: msg("@ requires a cut buffer name (a-z)");
876: }
877:
878: /* get the contents of the buffer */
879: result = cb2str(*extra, buf, (unsigned)(sizeof buf));
880: if (result <= 0)
881: {
882: msg("buffer \"%c is empty", *extra);
883: }
884: else if (result >= sizeof buf)
885: {
886: msg("buffer \"%c is too large to execute", *extra);
887: }
888: else
889: {
890: /* execute the contents of the buffer as ex commands */
891: exstring(buf, result);
892: }
893:
894: nest = FALSE;
895: }
896: #endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.