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