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