Commit | Line | Data |
---|---|---|
f5bcab4b WJ |
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" | |
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 | } | |
467 | tmpblk.c[len++] = ' '; | |
468 | } | |
469 | } | |
470 | strcpy(tmpblk.c + len, scan); | |
471 | len += strlen(scan); | |
472 | } | |
473 | tmpblk.c[len++] = '\n'; | |
474 | tmpblk.c[len] = '\0'; | |
475 | ||
476 | /* make the change */ | |
477 | ChangeText | |
478 | { | |
479 | frommark &= ~(BLKSIZE - 1); | |
480 | tomark &= ~(BLKSIZE - 1); | |
481 | tomark += BLKSIZE; | |
482 | change(frommark, tomark, tmpblk.c); | |
483 | } | |
484 | ||
485 | /* Reporting... */ | |
486 | rptlines = markline(tomark) - markline(frommark) - 1L; | |
487 | rptlabel = "joined"; | |
488 | } | |
489 | ||
490 | ||
491 | ||
492 | /*ARGSUSED*/ | |
493 | void cmd_shift(frommark, tomark, cmd, bang, extra) | |
494 | MARK frommark; | |
495 | MARK tomark; | |
496 | CMD cmd; | |
497 | int bang; | |
498 | char *extra; | |
499 | { | |
500 | long l; /* line number counter */ | |
501 | int oldidx; /* number of chars previously used for indent */ | |
502 | int newidx; /* number of chars in the new indent string */ | |
503 | int oldcol; /* previous indent amount */ | |
504 | int newcol; /* new indent amount */ | |
505 | char *text; /* pointer to the old line's text */ | |
506 | ||
507 | ChangeText | |
508 | { | |
509 | /* for each line to shift... */ | |
510 | for (l = markline(frommark); l <= markline(tomark); l++) | |
511 | { | |
512 | /* get the line - ignore empty lines unless ! mode */ | |
513 | text = fetchline(l); | |
514 | if (!*text && !bang) | |
515 | continue; | |
516 | ||
517 | /* calc oldidx and oldcol */ | |
518 | for (oldidx = 0, oldcol = 0; | |
519 | text[oldidx] == ' ' || text[oldidx] == '\t'; | |
520 | oldidx++) | |
521 | { | |
522 | if (text[oldidx] == ' ') | |
523 | { | |
524 | oldcol += 1; | |
525 | } | |
526 | else | |
527 | { | |
528 | oldcol += *o_tabstop - (oldcol % *o_tabstop); | |
529 | } | |
530 | } | |
531 | ||
532 | /* calc newcol */ | |
533 | if (cmd == CMD_SHIFTR) | |
534 | { | |
535 | newcol = oldcol + (*o_shiftwidth & 0xff); | |
536 | } | |
537 | else | |
538 | { | |
539 | newcol = oldcol - (*o_shiftwidth & 0xff); | |
540 | if (newcol < 0) | |
541 | newcol = 0; | |
542 | } | |
543 | ||
544 | /* if no change, then skip to next line */ | |
545 | if (oldcol == newcol) | |
546 | continue; | |
547 | ||
548 | /* build a new indent string */ | |
549 | newidx = 0; | |
550 | if (*o_autotab) | |
551 | { | |
552 | while (newcol >= *o_tabstop) | |
553 | { | |
554 | tmpblk.c[newidx++] = '\t'; | |
555 | newcol -= *o_tabstop; | |
556 | } | |
557 | } | |
558 | while (newcol > 0) | |
559 | { | |
560 | tmpblk.c[newidx++] = ' '; | |
561 | newcol--; | |
562 | } | |
563 | tmpblk.c[newidx] = '\0'; | |
564 | ||
565 | /* change the old indent string into the new */ | |
566 | change(MARK_AT_LINE(l), MARK_AT_LINE(l) + oldidx, tmpblk.c); | |
567 | } | |
568 | } | |
569 | ||
570 | /* Reporting... */ | |
571 | rptlines = markline(tomark) - markline(frommark) + 1L; | |
572 | if (cmd == CMD_SHIFTR) | |
573 | { | |
574 | rptlabel = ">ed"; | |
575 | } | |
576 | else | |
577 | { | |
578 | rptlabel = "<ed"; | |
579 | } | |
580 | } | |
581 | ||
582 | ||
583 | /*ARGSUSED*/ | |
584 | void cmd_read(frommark, tomark, cmd, bang, extra) | |
585 | MARK frommark; | |
586 | MARK tomark; | |
587 | CMD cmd; | |
588 | int bang; | |
589 | char *extra; | |
590 | { | |
591 | int fd, rc; /* used while reading from the file */ | |
592 | char *scan; /* used for finding NUL characters */ | |
593 | int hadnul; /* boolean: any NULs found? */ | |
594 | int addnl; /* boolean: forced to add newlines? */ | |
595 | int len; /* number of chars in current line */ | |
596 | long lines; /* number of lines in current block */ | |
597 | struct stat statb; | |
598 | ||
599 | /* special case: if ":r !cmd" then let the filter() function do it */ | |
600 | if (extra[0] == '!') | |
601 | { | |
602 | filter(frommark, MARK_UNSET, extra + 1, TRUE); | |
603 | return; | |
604 | } | |
605 | ||
606 | /* open the file */ | |
607 | fd = open(extra, O_RDONLY); | |
608 | if (fd < 0) | |
609 | { | |
610 | msg("Can't open \"%s\"", extra); | |
611 | return; | |
612 | } | |
613 | ||
614 | #ifndef CRUNCH | |
615 | if (stat(extra, &statb) < 0) | |
616 | { | |
617 | msg("Can't stat \"%s\"", extra); | |
618 | } | |
619 | # if TOS | |
620 | if (statb.st_mode & S_IJDIR) | |
621 | # else | |
622 | # if OSK | |
623 | if (statb.st_mode & S_IFDIR) | |
624 | # else | |
625 | if ((statb.st_mode & S_IFMT) != S_IFREG) | |
626 | # endif | |
627 | # endif | |
628 | { | |
629 | msg("\"%s\" is not a regular file", extra); | |
630 | return; | |
631 | } | |
632 | #endif /* not CRUNCH */ | |
633 | ||
634 | /* get blocks from the file, and add them */ | |
635 | ChangeText | |
636 | { | |
637 | /* insertion starts at the line following frommark */ | |
638 | tomark = frommark = (frommark | (BLKSIZE - 1L)) + 1L; | |
639 | len = 0; | |
640 | hadnul = addnl = FALSE; | |
641 | ||
642 | /* add an extra newline, so partial lines at the end of | |
643 | * the file don't trip us up | |
644 | */ | |
645 | add(tomark, "\n"); | |
646 | ||
647 | /* for each chunk of text... */ | |
648 | while ((rc = tread(fd, tmpblk.c, BLKSIZE - 1)) > 0) | |
649 | { | |
650 | /* count newlines, convert NULs, etc. ... */ | |
651 | for (lines = 0, scan = tmpblk.c; rc > 0; rc--, scan++) | |
652 | { | |
653 | /* break up long lines */ | |
654 | if (*scan != '\n' && len + 2 > BLKSIZE) | |
655 | { | |
656 | *scan = '\n'; | |
657 | addnl = TRUE; | |
658 | } | |
659 | ||
660 | /* protect against NUL chars in file */ | |
661 | if (!*scan) | |
662 | { | |
663 | *scan = 0x80; | |
664 | hadnul = TRUE; | |
665 | } | |
666 | ||
667 | /* starting a new line? */ | |
668 | if (*scan == '\n') | |
669 | { | |
670 | /* reset length at newline */ | |
671 | len = 0; | |
672 | lines++; | |
673 | } | |
674 | else | |
675 | { | |
676 | len++; | |
677 | } | |
678 | } | |
679 | ||
680 | /* add the text */ | |
681 | *scan = '\0'; | |
682 | add(tomark, tmpblk.c); | |
683 | tomark += MARK_AT_LINE(lines) + len - markidx(tomark); | |
684 | } | |
685 | ||
686 | /* if partial last line, then retain that first newline */ | |
687 | if (len > 0) | |
688 | { | |
689 | msg("Last line had no newline"); | |
690 | tomark += BLKSIZE; /* <- for the rptlines calc */ | |
691 | } | |
692 | else /* delete that first newline */ | |
693 | { | |
694 | delete(tomark, (tomark | (BLKSIZE - 1L)) + 1L); | |
695 | } | |
696 | } | |
697 | ||
698 | /* close the file */ | |
699 | close(fd); | |
700 | ||
701 | /* Reporting... */ | |
702 | rptlines = markline(tomark) - markline(frommark); | |
703 | rptlabel = "read"; | |
704 | if (mode == MODE_EX) | |
705 | { | |
706 | cursor = (tomark & ~BLKSIZE) - BLKSIZE; | |
707 | } | |
708 | else | |
709 | { | |
710 | cursor = frommark; | |
711 | } | |
712 | ||
713 | if (addnl) | |
714 | msg("Newlines were added to break up long lines"); | |
715 | if (hadnul) | |
716 | msg("NULs were converted to 0x80"); | |
717 | } | |
718 | ||
719 | ||
720 | ||
721 | /*ARGSUSED*/ | |
722 | void cmd_undo(frommark, tomark, cmd, bang, extra) | |
723 | MARK frommark; | |
724 | MARK tomark; | |
725 | CMD cmd; | |
726 | int bang; | |
727 | char *extra; | |
728 | { | |
729 | undo(); | |
730 | } | |
731 | ||
732 | ||
733 | /* print the selected lines */ | |
734 | /*ARGSUSED*/ | |
735 | void cmd_print(frommark, tomark, cmd, bang, extra) | |
736 | MARK frommark; | |
737 | MARK tomark; | |
738 | CMD cmd; | |
739 | int bang; | |
740 | char *extra; | |
741 | { | |
742 | REG char *scan; | |
743 | REG long l; | |
744 | REG int col; | |
745 | ||
746 | for (l = markline(frommark); l <= markline(tomark); l++) | |
747 | { | |
748 | /* display a line number, if CMD_NUMBER */ | |
749 | if (cmd == CMD_NUMBER) | |
750 | { | |
751 | sprintf(tmpblk.c, "%6ld ", l); | |
752 | qaddstr(tmpblk.c); | |
753 | col = 8; | |
754 | } | |
755 | else | |
756 | { | |
757 | col = 0; | |
758 | } | |
759 | ||
760 | /* get the next line & display it */ | |
761 | for (scan = fetchline(l); *scan; scan++) | |
762 | { | |
763 | /* expand tabs to the proper width */ | |
764 | if (*scan == '\t' && cmd != CMD_LIST) | |
765 | { | |
766 | do | |
767 | { | |
768 | qaddch(' '); | |
769 | col++; | |
770 | } while (col % *o_tabstop != 0); | |
771 | } | |
772 | else if (*scan > 0 && *scan < ' ' || *scan == '\177') | |
773 | { | |
774 | qaddch('^'); | |
775 | qaddch(*scan ^ 0x40); | |
776 | col += 2; | |
777 | } | |
778 | else if ((*scan & 0x80) && cmd == CMD_LIST) | |
779 | { | |
780 | sprintf(tmpblk.c, "\\%03o", UCHAR(*scan)); | |
781 | qaddstr(tmpblk.c); | |
782 | col += 4; | |
783 | } | |
784 | else | |
785 | { | |
786 | qaddch(*scan); | |
787 | col++; | |
788 | } | |
789 | ||
790 | /* wrap at the edge of the screen */ | |
791 | if (!has_AM && col >= COLS) | |
792 | { | |
793 | addch('\n'); | |
794 | col -= COLS; | |
795 | } | |
796 | } | |
797 | if (cmd == CMD_LIST) | |
798 | { | |
799 | qaddch('$'); | |
800 | } | |
801 | addch('\n'); | |
802 | exrefresh(); | |
803 | } | |
804 | } | |
805 | ||
806 | ||
807 | /* move or copy selected lines */ | |
808 | /*ARGSUSED*/ | |
809 | void cmd_move(frommark, tomark, cmd, bang, extra) | |
810 | MARK frommark; | |
811 | MARK tomark; | |
812 | CMD cmd; | |
813 | int bang; | |
814 | char *extra; | |
815 | { | |
816 | MARK destmark; | |
817 | ||
818 | /* parse the destination linespec. No defaults. Line 0 is okay */ | |
819 | destmark = cursor; | |
820 | if (!strcmp(extra, "0")) | |
821 | { | |
822 | destmark = 0L; | |
823 | } | |
824 | else if (linespec(extra, &destmark) == extra || !destmark) | |
825 | { | |
826 | msg("invalid destination address"); | |
827 | return; | |
828 | } | |
829 | ||
830 | /* flesh the marks out to encompass whole lines */ | |
831 | frommark &= ~(BLKSIZE - 1); | |
832 | tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE; | |
833 | destmark = (destmark & ~(BLKSIZE - 1)) + BLKSIZE; | |
834 | ||
835 | /* make sure the destination is valid */ | |
836 | if (cmd == CMD_MOVE && destmark >= frommark && destmark < tomark) | |
837 | { | |
838 | msg("invalid destination address"); | |
839 | } | |
840 | ||
841 | /* Do it */ | |
842 | ChangeText | |
843 | { | |
844 | /* save the text to a cut buffer */ | |
845 | cutname('\0'); | |
846 | cut(frommark, tomark); | |
847 | ||
848 | /* if we're not copying, delete the old text & adjust destmark */ | |
849 | if (cmd != CMD_COPY) | |
850 | { | |
851 | delete(frommark, tomark); | |
852 | if (destmark >= frommark) | |
853 | { | |
854 | destmark -= (tomark - frommark); | |
855 | } | |
856 | } | |
857 | ||
858 | /* add the new text */ | |
859 | paste(destmark, FALSE, FALSE); | |
860 | } | |
861 | ||
862 | /* move the cursor to the last line of the moved text */ | |
863 | cursor = destmark + (tomark - frommark) - BLKSIZE; | |
864 | if (cursor < MARK_FIRST || cursor >= MARK_LAST + BLKSIZE) | |
865 | { | |
866 | cursor = MARK_LAST; | |
867 | } | |
868 | ||
869 | /* Reporting... */ | |
870 | rptlabel = ( (cmd == CMD_COPY) ? "copied" : "moved" ); | |
871 | } | |
872 | ||
873 | ||
874 | ||
875 | /* execute EX commands from a file */ | |
876 | /*ARGSUSED*/ | |
877 | void cmd_source(frommark, tomark, cmd, bang, extra) | |
878 | MARK frommark; | |
879 | MARK tomark; | |
880 | CMD cmd; | |
881 | int bang; | |
882 | char *extra; | |
883 | { | |
884 | /* must have a filename */ | |
885 | if (!*extra) | |
886 | { | |
887 | msg("\"source\" requires a filename"); | |
888 | return; | |
889 | } | |
890 | ||
891 | doexrc(extra); | |
892 | } | |
893 | ||
894 | ||
895 | #ifndef NO_AT | |
896 | /*ARGSUSED*/ | |
897 | void cmd_at(frommark, tomark, cmd, bang, extra) | |
898 | MARK frommark; | |
899 | MARK tomark; | |
900 | CMD cmd; | |
901 | int bang; | |
902 | char *extra; | |
903 | { | |
904 | static nest = FALSE; | |
905 | int result; | |
906 | char buf[MAXRCLEN]; | |
907 | ||
908 | /* don't allow nested macros */ | |
909 | if (nest) | |
910 | { | |
911 | msg("@ macros can't be nested"); | |
912 | return; | |
913 | } | |
914 | nest = TRUE; | |
915 | ||
916 | /* require a buffer name */ | |
917 | if (*extra == '"') | |
918 | extra++; | |
919 | if (!*extra || !isascii(*extra) ||!islower(*extra)) | |
920 | { | |
921 | msg("@ requires a cut buffer name (a-z)"); | |
922 | } | |
923 | ||
924 | /* get the contents of the buffer */ | |
925 | result = cb2str(*extra, buf, (unsigned)(sizeof buf)); | |
926 | if (result <= 0) | |
927 | { | |
928 | msg("buffer \"%c is empty", *extra); | |
929 | } | |
930 | else if (result >= sizeof buf) | |
931 | { | |
932 | msg("buffer \"%c is too large to execute", *extra); | |
933 | } | |
934 | else | |
935 | { | |
936 | /* execute the contents of the buffer as ex commands */ | |
937 | exstring(buf, result, '\\'); | |
938 | } | |
939 | ||
940 | nest = FALSE; | |
941 | } | |
942 | #endif |