|
|
1 # define INCLUDE_CTYPE
2 # include "ed.h"
3 # include "edcmd.h"
4 # include "fileio.h"
5
6 /*
7 * This file defines the command subroutines for edcmd.c
8 */
9
10 extern char *skipst P((char*));
11 extern char *pattern P((char*, int, char*));
12 extern void cb_count P((cmdbuf*));
13 extern void not_in_global P((cmdbuf*));
14 extern void cb_do P((cmdbuf*, Int));
15 extern void cb_buf P((cmdbuf*, block));
16 extern void add P((cmdbuf*, Int, block, Int));
17 extern block delete P((cmdbuf*, Int, Int));
18 extern void change P((cmdbuf*, Int, Int, block));
19 extern void startblock P((cmdbuf*));
20 extern void addblock P((cmdbuf*, char*));
21 extern void endblock P((cmdbuf*));
22
23
24 /*
25 * NAME: find()
26 * DESCRIPTION: scan a line for a pattern. If the pattern is found, longjump
27 * out.
28 */
29 static void find(ptr, text)
30 char *ptr, *text;
31 {
32 register cmdbuf *cb;
33
34 cb = (cmdbuf *) ptr;
35 if (rx_exec(cb->regexp, text, 0, cb->ignorecase) > 0) {
36 longjmp(cb->env, TRUE);
37 }
38 cb->lineno++;
39 }
40
41 /*
42 * NAME: cmdbuf->search()
43 * DESCRIPTION: search a range of lines for the occurance of a pattern. When
44 * found, jump out immediately.
45 */
46 Int cb_search(cb, first, last, reverse)
47 cmdbuf *cb;
48 Int first, last;
49 int reverse;
50 {
51 if (setjmp(cb->env)) {
52 /* found */
53 return (reverse) ? last - cb->lineno : first + cb->lineno;
54 }
55
56 cb->lineno = 0;
57 cb->ignorecase = IGNORECASE(cb->vars);
58 eb_range(cb->edbuf, first, last, find, (char *) cb, reverse);
59 /* not found */
60 return 0;
61 }
62
63
64 /*
65 * NAME: println()
66 * DESCRIPTION: output a line of text. The format is decided by flags.
67 * Non-ascii characters (eight bit set) have no special processing.
68 */
69 static void println(ptr, text)
70 char *ptr;
71 register char *text;
72 {
73 char buffer[2 * MAX_LINE_SIZE + 14]; /* all ^x + number + list */
74 register cmdbuf *cb;
75 register char *p;
76
77 cb = (cmdbuf *) ptr;
78
79 if (cb->flags & CB_NUMBER) {
80 sprintf(buffer, "%6ld ", (long) cb->lineno++);
81 p = buffer + 8;
82 } else {
83 p = buffer;
84 }
85
86 while (*text != '\0') {
87 if (UCHAR(*text) < ' ') {
88 /* control character */
89 if (*text == HT && !(cb->flags & CB_LIST)) {
90 *p++ = HT;
91 } else {
92 *p++ = '^'; *p++ = (*text & 0x1f) + '@';
93 }
94 } else if (*text == 0x7f) {
95 /* DEL */
96 *p++ = '^'; *p++ = '?';
97 } else {
98 /* normal character */
99 *p++ = *text;
100 }
101 text++;
102 }
103 if (cb->flags & CB_LIST) {
104 *p++ = '$';
105 }
106 *p = '\0';
107 output("%s\012", buffer); /* LF */
108 }
109
110 /*
111 * NAME: cmdbuf->print()
112 * DESCRIPTION: print a range of lines, according to the format specified in
113 * the flags. Afterwards, the current line is set to the last line
114 * printed.
115 */
116 int cb_print(cb)
117 register cmdbuf *cb;
118 {
119 register char *p;
120
121 /* handle flags right now */
122 p = cb->cmd;
123 for (;;) {
124 switch (*p++) {
125 case '-':
126 case '+':
127 case 'p':
128 /* ignore */
129 continue;
130
131 case 'l':
132 cb->flags |= CB_LIST;
133 continue;
134
135 case '#':
136 cb->flags |= CB_NUMBER;
137 continue;
138 }
139 cb->cmd = --p;
140 break;
141 }
142
143 cb->lineno = cb->first;
144 eb_range(cb->edbuf, cb->first, cb->last, println, (char *) cb, FALSE);
145 cb->this = cb->last;
146 return 0;
147 }
148
149 /*
150 * NAME: cmdbuf->list()
151 * DESCRIPTION: output a range of lines in a hopefully unambiguous format
152 */
153 int cb_list(cb)
154 cmdbuf *cb;
155 {
156 cb->flags |= CB_LIST;
157 return cb_print(cb);
158 }
159
160 /*
161 * NAME: cmdbuf->number()
162 * DESCRIPTION: output a range of lines preceded by line numbers
163 */
164 int cb_number(cb)
165 cmdbuf *cb;
166 {
167 cb->flags |= CB_NUMBER;
168 return cb_print(cb);
169 }
170
171 /*
172 * NAME: cmdbuf->page()
173 * DESCRIPTION: show a page of lines
174 */
175 int cb_page(cb)
176 register cmdbuf *cb;
177 {
178 register Int offset, window;
179
180 if (cb->edbuf->lines == 0) {
181 error("No lines in buffer");
182 }
183
184 window = WINDOW(cb->vars);
185 switch (*(cb->cmd)++) {
186 default: /* next line */
187 cb->cmd--;
188 cb->this++;
189 case '+': /* top */
190 offset = 0;
191 break;
192
193 case '-': /* bottom */
194 offset = 1 - window;
195 break;
196
197 case '.': /* middle */
198 offset = 1 - (window + 1) / 2;
199 break;
200 }
201
202 /* set first */
203 if (cb->first < 0) {
204 cb->first = cb->this;
205 }
206 cb->first += offset;
207 if (cb->first <= 0) {
208 cb->first = 1;
209 } else if (cb->first > cb->edbuf->lines) {
210 cb->first = cb->edbuf->lines;
211 }
212
213 /* set last */
214 cb->last = cb->first + window - 1;
215 if (cb->last < cb->first) {
216 cb->last = cb->first;
217 } else if (cb->last > cb->edbuf->lines) {
218 cb->last = cb->edbuf->lines;
219 }
220
221 return cb_print(cb);
222 }
223
224 /*
225 * NAME: cmdbuf->assign()
226 * DESCRIPTION: show the specified line number
227 */
228 int cb_assign(cb)
229 register cmdbuf *cb;
230 {
231 output("%ld\012",
232 (long) (cb->first < 0) ? cb->edbuf->lines : cb->first); /* LF */
233 return 0;
234 }
235
236
237 /*
238 * NAME: cmdbuf->mark()
239 * DESCRIPTION: set a mark in the range [a-z] to line number
240 */
241 int cb_mark(cb)
242 register cmdbuf *cb;
243 {
244 if (!islower(cb->cmd[0])) {
245 error("Mark must specify a letter");
246 }
247 cb->mark[*(cb->cmd)++ - 'a'] = cb->first;
248 return 0;
249 }
250
251
252 /*
253 * NAME: cmdbuf->append()
254 * DESCRIPTION: append a block of lines, read from user, to edit buffer
255 */
256 int cb_append(cb)
257 register cmdbuf *cb;
258 {
259 not_in_global(cb);
260 cb_do(cb, cb->first);
261
262 startblock(cb);
263 cb->flags |= CB_INSERT;
264 return 0;
265 }
266
267 /*
268 * NAME: cmdbuf->insert()
269 * DESCRIPTION: insert a block of lines in the edit buffer
270 */
271 int cb_insert(cb)
272 cmdbuf *cb;
273 {
274 not_in_global(cb);
275 if (cb->first > 0) {
276 cb->first--;
277 }
278 return cb_append(cb);
279 }
280
281 /*
282 * NAME: cmdbuf->change()
283 * DESCRIPTION: change a subrange of lines in the edit buffer
284 */
285 int cb_change(cb)
286 cmdbuf *cb;
287 {
288 register Int *m;
289
290 not_in_global(cb);
291 cb_do(cb, cb->first);
292
293 /* erase marks of changed lines */
294 for (m = cb->mark; m < &cb->mark[26]; m++) {
295 if (*m >= cb->first && *m <= cb->last) {
296 *m = 0;
297 }
298 }
299
300 startblock(cb);
301 cb->flags |= CB_INSERT | CB_CHANGE;
302 return 0;
303 }
304
305
306 /*
307 * NAME: cmdbuf->delete()
308 * DESCRIPTION: delete a subrange of lines in the edit buffer
309 */
310 int cb_delete(cb)
311 register cmdbuf *cb;
312 {
313 cb_do(cb, cb->first);
314
315 cb_buf(cb, delete(cb, cb->first, cb->last));
316
317 cb->edit++;
318
319 return RET_FLAGS;
320 }
321
322 /*
323 * NAME: cmdbuf->copy()
324 * DESCRIPTION: copy a subrange of lines in the edit buffer
325 */
326 int cb_copy(cb)
327 register cmdbuf *cb;
328 {
329 cb_do(cb, cb->a_addr);
330 add(cb, cb->a_addr, eb_yank(cb->edbuf, cb->first, cb->last),
331 cb->last - cb->first + 1);
332
333 cb->edit++;
334
335 return RET_FLAGS;
336 }
337
338 /*
339 * NAME: cmdbuf->move()
340 * DESCRIPTION: move a subrange of lines in the edit buffer
341 */
342 int cb_move(cb)
343 register cmdbuf *cb;
344 {
345 Int mark[26];
346 register Int offset, *m1, *m2;
347
348 if (cb->a_addr >= cb->first - 1 && cb->a_addr <= cb->last) {
349 error("Move to moved line");
350 }
351
352 cb_do(cb, cb->first);
353 memset(mark, '\0', sizeof(mark));
354 if (cb->a_addr < cb->last) {
355 offset = cb->a_addr + 1 - cb->first;
356 } else {
357 offset = cb->a_addr - cb->last;
358 cb->a_addr -= cb->last - cb->first + 1;
359 }
360 /* make a copy of the marks of the lines to move */
361 for (m1 = mark, m2 = cb->mark; m1 < &mark[26]; m1++, m2++) {
362 if (*m2 >= cb->first && *m2 <= cb->last) {
363 *m1 = *m2;
364 } else {
365 *m1 = 0;
366 }
367 }
368 add(cb, cb->a_addr, delete(cb, cb->first, cb->last),
369 cb->last - cb->first + 1);
370 /* copy back adjusted marks of moved lines */
371 for (m1 = mark, m2 = cb->mark; m1 < &mark[26]; m1++, m2++) {
372 if (*m1 != 0) {
373 *m2 = *m1 + offset;
374 }
375 }
376
377 cb->edit++;
378
379 return RET_FLAGS;
380 }
381
382 /*
383 * NAME: cmdbuf->put()
384 * DESCRIPTION: put a block in the edit buffer
385 */
386 int cb_put(cb)
387 register cmdbuf *cb;
388 {
389 register block b;
390
391 if (isalpha(cb->a_buffer)) {
392 /* 'a' and 'A' both refer to buffer 'a' */
393 b = cb->zbuf[tolower(cb->a_buffer) - 'a'];
394 } else {
395 b = cb->buf;
396 }
397 if (b == (block) 0) {
398 error("Nothing in buffer");
399 }
400
401 cb_do(cb, cb->first);
402 add(cb, cb->first, b, bk_size(cb->edbuf->lb, b));
403
404 cb->edit++;
405
406 return RET_FLAGS;
407 }
408
409 /*
410 * NAME: cmdbuf->yank()
411 * DESCRIPTION: yank a block of lines from the edit buffer
412 */
413 int cb_yank(cb)
414 register cmdbuf *cb;
415 {
416 cb_buf(cb, eb_yank(cb->edbuf, cb->first, cb->last));
417 return 0;
418 }
419
420
421 /*
422 * NAME: shift()
423 * DESCRIPTION: shift a line left or right
424 */
425 static void shift(ptr, text)
426 char *ptr;
427 register char *text;
428 {
429 register cmdbuf *cb;
430 register int idx;
431
432 cb = (cmdbuf *) ptr;
433
434 /* first determine the number of leading spaces */
435 idx = 0;
436 while (*text == ' ' || *text == HT) {
437 if (*text++ == ' ') {
438 idx++;
439 } else {
440 idx = (idx + 8) & ~7;
441 }
442 }
443
444 if (*text == '\0') {
445 /* don't shift lines with ws only */
446 addblock(cb, text);
447 cb->lineno++;
448 } else {
449 idx += cb->shift;
450 if (idx < MAX_LINE_SIZE) {
451 char buffer[MAX_LINE_SIZE];
452 register char *p;
453
454 p = buffer;
455 /* fill with leading ws */
456 while (idx >= 8) {
457 *p++ = HT;
458 idx -= 8;
459 }
460 while (idx > 0) {
461 *p++ = ' ';
462 --idx;
463 }
464 if (p - buffer + strlen(text) < MAX_LINE_SIZE) {
465 strcpy(p, text);
466 addblock(cb, buffer);
467 cb->lineno++;
468 return;
469 }
470 }
471
472 /* Error: line too long. Finish block of lines already shifted. */
473 cb->last = cb->lineno;
474 endblock(cb);
475 error("Result of shift would be too long");
476 }
477 }
478
479 /*
480 * NAME: cmdbuf->shift()
481 * DESCRIPTION: shift a range of lines left or right
482 */
483 static int cb_shift(cb)
484 register cmdbuf *cb;
485 {
486 cb_do(cb, cb->first);
487 startblock(cb);
488 cb->lineno = cb->first - 1;
489 cb->flags |= CB_CHANGE;
490 eb_range(cb->edbuf, cb->first, cb->last, shift, (char *) cb, FALSE);
491 endblock(cb);
492
493 return RET_FLAGS;
494 }
495
496 /*
497 * NAME: cmdbuf->lshift()
498 * DESCRIPTION: shift a range of lines to the left
499 */
500 int cb_lshift(cb)
501 register cmdbuf *cb;
502 {
503 cb->shift = -SHIFTWIDTH(cb->vars);
504 return cb_shift(cb);
505 }
506
507 /*
508 * NAME: cmdbuf->rshift()
509 * DESCRIPTION: shift a range of lines to the right
510 */
511 int cb_rshift(cb)
512 register cmdbuf *cb;
513 {
514 cb->shift = SHIFTWIDTH(cb->vars);
515 return cb_shift(cb);
516 }
517
518
519 # define STACKSZ 1024 /* size of indent stack */
520
521 /* token definitions in indent */
522 # define SEMICOLON 0
523 # define LBRACKET 1
524 # define RBRACKET 2
525 # define LOPERATOR 3
526 # define ROPERATOR 4
527 # define LHOOK 5
528 # define RHOOK 6
529 # define TOKEN 7
530 # define ELSE 8
531 # define IF 9
532 # define FOR 10 /* WHILE, RLIMIT */
533 # define DO 11
534 # define EOT 12
535
536 /*
537 * NAME: noshift()
538 * DESCRIPTION: add this line to the current block without shifting it
539 */
540 static void noshift(cb, text)
541 cmdbuf *cb;
542 char *text;
543 {
544 addblock(cb, text);
545 cb->lineno++;
546 }
547
548 /*
549 * NAME: indent()
550 * DESCRIPTION: Parse and indent a line of text. This isn't perfect, as
551 * keywords could be defined as macros, comments are very hard to
552 * handle properly, (, [ and ({ will match any of ), ] and }),
553 * and last but not least everyone has his own taste of
554 * indentation.
555 */
556 static void indent(ptr, text)
557 char *ptr, *text;
558 {
559 static char f[] = { 7, 1, 7, 1, 2, 1, 6, 4, 2, 6, 7, 2, 0, };
560 static char g[] = { 2, 2, 1, 7, 1, 5, 1, 3, 6, 2, 2, 2, 0, };
561 char ident[MAX_LINE_SIZE];
562 char line[MAX_LINE_SIZE];
563 register cmdbuf *cb;
564 register char *p, *sp;
565 register int *ip, idx;
566 register int top, token;
567 char *start;
568 bool do_indent;
569
570 cb = (cmdbuf *) ptr;
571
572 do_indent = FALSE;
573 idx = 0;
574 p = text = strcpy(line, text);
575
576 /* process status vars */
577 if (cb->quote != '\0') {
578 cb->shift = 0; /* in case a comment starts on this line */
579 noshift(cb, p);
580 } else if ((cb->flags & CB_PPCONTROL) || *p == '#') {
581 noshift(cb, p);
582 while (*p != '\0') {
583 if (*p == '\\' && *++p == '\0') {
584 cb->flags |= CB_PPCONTROL;
585 return;
586 }
587 p++;
588 }
589 cb->flags &= ~CB_PPCONTROL;
590 return;
591 } else {
592 /* count leading ws */
593 while (*p == ' ' || *p == HT) {
594 if (*p++ == ' ') {
595 idx++;
596 } else {
597 idx = (idx + 8) & ~7;
598 }
599 }
600 if (*p == '\0') {
601 noshift(cb, p);
602 return;
603 } else if (cb->flags & CB_COMMENT) {
604 shift(cb, text); /* use previous shift */
605 } else {
606 do_indent = TRUE;
607 }
608 }
609
610 /* process this line */
611 start = p;
612 while (*p != '\0') {
613
614 /* lexical scanning: find the next token */
615 ident[0] = '\0';
616 if (cb->flags & CB_COMMENT) {
617 /* comment */
618 while (*p != '*') {
619 if (*p == '\0') {
620 return;
621 }
622 p++;
623 }
624 while (*p == '*') {
625 p++;
626 }
627 if (*p == '/') {
628 cb->flags &= ~CB_COMMENT;
629 p++;
630 }
631 continue;
632
633 } else if (cb->quote != '\0') {
634 /* string or character constant */
635 for (;;) {
636 if (*p == cb->quote) {
637 cb->quote = '\0';
638 p++;
639 break;
640 } else if (*p == '\0') {
641 cb->last = cb->lineno;
642 endblock(cb);
643 error("Unterminated string");
644 } else if (*p == '\\' && *++p == '\0') {
645 break;
646 }
647 p++;
648 }
649 token = TOKEN;
650
651 } else {
652 switch (*p++) {
653 case ' ': /* white space */
654 case HT:
655 continue;
656
657 case '\'': /* start of string */
658 case '"':
659 cb->quote = p[-1];
660 continue;
661
662 case '/':
663 if (*p == '*') { /* start of comment */
664 cb->flags |= CB_COMMENT;
665 if (do_indent) {
666 /* this line hasn't been indented yet */
667 cb->shift = cb->ind[0] - idx;
668 shift(cb, text);
669 do_indent = FALSE;
670 } else {
671 register char *q;
672 register int idx2;
673
674 /*
675 * find how much the comment has shifted, so the same
676 * shift can be used if the comment continues on the
677 * next line
678 */
679 idx2 = cb->ind[0];
680 for (q = start; q < p - 1;) {
681 if (*q++ == HT) {
682 idx = (idx + 8) & ~7;
683 idx2 = (idx2 + 8) & ~7;
684 } else {
685 idx++;
686 idx2++;
687 }
688 }
689 cb->shift = idx2 - idx;
690 }
691 p++;
692 continue;
693 }
694 token = TOKEN;
695 break;
696
697 case '{':
698 token = LBRACKET;
699 break;
700
701 case '(':
702 if (cb->flags & CB_JSKEYWORD) {
703 /*
704 * LOPERATOR & ROPERATOR are a kludge. The operator
705 * precedence parser that is used could not work if
706 * parenthesis after keywords was not treated specially.
707 */
708 token = LOPERATOR;
709 break;
710 }
711 if (*p == '{') {
712 p++; /* ({ is one token */
713 }
714 case '[':
715 token = LHOOK;
716 break;
717
718 case '}':
719 if (*p != ')') {
720 token = RBRACKET;
721 break;
722 }
723 p++;
724 /* }) is one token; fall through */
725 case ')':
726 case ']':
727 token = RHOOK;
728 break;
729
730 case ';':
731 token = SEMICOLON;
732 break;
733
734 default:
735 if (isalpha(*--p) || *p == '_') {
736 register char *q;
737
738 /* Identifier. See if it's a keyword. */
739 q = ident;
740 do {
741 *q++ = *p++;
742 } while (isalnum(*p) || *p == '_');
743 *q = '\0';
744
745 if (strcmp(ident, "if") == 0) token = IF;
746 else if (strcmp(ident, "else") == 0) token = ELSE;
747 else if (strcmp(ident, "for") == 0 ||
748 strcmp(ident, "while") == 0 ||
749 strcmp(ident, "rlimits") == 0) token = FOR;
750 else if (strcmp(ident, "do") == 0) token = DO;
751 else /* not a keyword */ token = TOKEN;
752 } else {
753 /* anything else is a "token" */
754 p++;
755 token = TOKEN;
756 }
757 break;
758 }
759 }
760
761 /* parse */
762 sp = cb->stack;
763 ip = cb->ind;
764 for (;;) {
765 top = *sp;
766 if (top == LOPERATOR && token == RHOOK) {
767 /* ) after LOPERATOR is ROPERATOR */
768 token = ROPERATOR;
769 }
770
771 if (f[top] <= g[token]) { /* shift the token on the stack */
772 register int i;
773
774 if (sp == cb->stackbot) {
775 /* out of stack. Finish already indented block. */
776 cb->last = cb->lineno;
777 endblock(cb);
778 error("Nesting too deep");
779 }
780
781 /* handle indentation */
782 i = *ip;
783 /* if needed, reduce indentation prior to shift */
784 if ((token == LBRACKET &&
785 (*sp == ROPERATOR || *sp == ELSE || *sp == DO)) ||
786 token == RBRACKET ||
787 (token == IF && *sp == ELSE)) {
788 /* back up */
789 i -= SHIFTWIDTH(cb->vars);
790 }
791 /* shift the current line, if appropriate */
792 if (do_indent) {
793 cb->shift = i - idx;
794 if (i > 0 && token != RHOOK &&
795 (*sp == LOPERATOR || *sp == LHOOK)) {
796 /* half indent after ( [ ({ (HACK!) */
797 cb->shift += SHIFTWIDTH(cb->vars) / 2;
798 } else if (token == TOKEN && *sp == LBRACKET &&
799 (strcmp(ident, "case") == 0 ||
800 strcmp(ident, "default") == 0)) {
801 /* back up if this is a switch label */
802 cb->shift -= SHIFTWIDTH(cb->vars);
803 }
804 shift(cb, text);
805 do_indent = FALSE;
806 }
807 /* change indentation after current token */
808 if (token == LBRACKET || token == ROPERATOR || token == ELSE ||
809 token == DO) {
810 /* add indentation */
811 i += SHIFTWIDTH(cb->vars);
812 } else if (token == SEMICOLON &&
813 (*sp == ROPERATOR || *sp == ELSE)) {
814 /* in case it is followed by a comment */
815 i -= SHIFTWIDTH(cb->vars);
816 }
817
818 *--sp = token;
819 *--ip = i;
820 break;
821 }
822
823 /* reduce handle */
824 do {
825 top = *sp++;
826 ip++;
827 } while (f[*sp] >= g[top]);
828 }
829 cb->stack = sp;
830 cb->ind = ip;
831 if (token >= IF) { /* but not ELSE */
832 cb->flags |= CB_JSKEYWORD;
833 } else {
834 cb->flags &= ~CB_JSKEYWORD;
835 }
836 }
837 }
838
839 /*
840 * NAME: cmdbuf->indent()
841 * DESCRIPTION: indent a range of lines
842 */
843 int cb_indent(cb)
844 register cmdbuf *cb;
845 {
846 char s[STACKSZ];
847 int i[STACKSZ];
848
849 /* setup stacks */
850 cb->stackbot = s;
851 cb->stack = s + STACKSZ - 1;
852 cb->stack[0] = EOT;
853 cb->ind = i + STACKSZ - 1;
854 cb->ind[0] = 0;
855 cb->quote = '\0';
856
857 cb_do(cb, cb->first);
858 startblock(cb);
859 cb->lineno = cb->first - 1;
860 cb->flags |= CB_CHANGE;
861 cb->flags &= ~(CB_PPCONTROL | CB_COMMENT | CB_JSKEYWORD);
862 eb_range(cb->edbuf, cb->first, cb->last, indent, (char *) cb, FALSE);
863 endblock(cb);
864
865 return 0;
866 }
867
868
869 /*
870 * NAME: join()
871 * DESCRIPTION: join a string to the one already in the join buffer
872 */
873 static void join(ptr, text)
874 char *ptr;
875 register char *text;
876 {
877 register cmdbuf *cb;
878 register char *p;
879
880 cb = (cmdbuf *) ptr;
881
882 p = cb->buffer + cb->buflen;
883 if (cb->buflen != 0 && !(cb->flags & CB_EXCL)) {
884 /* do special processing */
885 text = skipst(text);
886 if (*text != '\0' && *text != ')' && p[-1] != ' ' && p[-1] != HT) {
887 if (p[-1] == '.') {
888 *p++ = ' ';
889 }
890 *p++ = ' ';
891 }
892 cb->buflen = p - cb->buffer;
893 }
894 cb->buflen += strlen(text);
895 if (cb->buflen >= MAX_LINE_SIZE) {
896 error("Result of join would be too long");
897 }
898 strcpy(p, text);
899 }
900
901 /*
902 * NAME: cmdbuf->join()
903 * DESCRIPTION: join a range of lines in the edit buffer
904 */
905 int cb_join(cb)
906 register cmdbuf *cb;
907 {
908 char buf[MAX_LINE_SIZE + 1];
909 register Int *m;
910
911 if (cb->edbuf->lines == 0) {
912 error("No lines in buffer");
913 }
914 if (cb->first < 0) {
915 cb->first = cb->this;
916 }
917 if (cb->last < 0) {
918 cb->last = (cb->first == cb->edbuf->lines) ? cb->first : cb->first + 1;
919 }
920
921 cb_do(cb, cb->first);
922
923 cb->this = cb->othis = cb->first;
924 buf[0] = '\0';
925 cb->buffer = buf;
926 cb->buflen = 0;
927 eb_range(cb->edbuf, cb->first, cb->last, join, (char *) cb, FALSE);
928
929 /* erase marks for joined lines */
930 for (m = cb->mark; m < &cb->mark[26]; m++) {
931 if (*m > cb->first && *m <= cb->last) {
932 *m = 0;
933 }
934 }
935
936 cb->flags |= CB_CHANGE;
937 startblock(cb);
938 addblock(cb, buf);
939 endblock(cb);
940
941 return RET_FLAGS;
942 }
943
944
945 /*
946 * NAME: sub()
947 * DESCRIPTION: add a string to the current substitute buffer
948 */
949 static void sub(cb, text, size)
950 register cmdbuf *cb;
951 char *text;
952 unsigned int size;
953 {
954 register char *p, *q;
955 register unsigned int i;
956
957 i = size;
958 if (cb->buflen + i >= MAX_LINE_SIZE) {
959 if (cb->flags & CB_CURRENTBLK) {
960 /* finish already processed block */
961 endblock(cb);
962 }
963 cb->this = cb->othis = cb->lineno;
964 error("Line overflow in substitute");
965 }
966
967 p = cb->buffer + cb->buflen;
968 q = text;
969 if (cb->flags & CB_TLOWER) { /* lowercase one letter */
970 *p++ = tolower(*q);
971 q++;
972 cb->flags &= ~CB_TLOWER;
973 --i;
974 } else if (cb->flags & CB_TUPPER) { /* uppercase one letter */
975 *p++ = toupper(*q);
976 q++;
977 cb->flags &= ~CB_TUPPER;
978 --i;
979 }
980
981 if (cb->flags & CB_LOWER) { /* lowercase string */
982 while (i > 0) {
983 *p++ = tolower(*q);
984 q++;
985 --i;
986 }
987 } else if (cb->flags & CB_UPPER) { /* uppercase string */
988 while (i > 0) {
989 *p++ = toupper(*q);
990 q++;
991 --i;
992 }
993 } else if (i > 0) { /* don't change case */
994 memcpy(p, q, i);
995 }
996 cb->buflen += size;
997 }
998
999 /*
1000 * NAME: subst()
1001 * DESCRIPTION: do substitutions in a line. If something is substituted on line
1002 * N, and the next substitution happens on line N + 2, line N + 1
1003 * is joined in the new block also.
1004 */
1005 static void subst(ptr, text)
1006 char *ptr;
1007 register char *text;
1008 {
1009 char line[MAX_LINE_SIZE];
1010 register cmdbuf *cb;
1011 register int idx, size;
1012 register char *p;
1013 register Int *k, *l;
1014 Int newlines;
1015 bool found;
1016
1017 cb = (cmdbuf *) ptr;
1018
1019 found = FALSE;
1020 newlines = 0;
1021 idx = 0;
1022
1023 /*
1024 * Because the write buffer might be flushed, and the text would
1025 * not remain in memory, use a local copy.
1026 */
1027 text = strcpy(line, text);
1028 while (rx_exec(cb->regexp, text, idx, IGNORECASE(cb->vars)) > 0) {
1029 if (cb->flags & CB_SKIPPED) {
1030 /*
1031 * add the previous line, in which nothing was substituted, to
1032 * the block. Has to be done here, before the contents of the buffer
1033 * are changed.
1034 */
1035 addblock(cb, cb->buffer);
1036 cb->flags &= ~CB_SKIPPED;
1037 /*
1038 * check if there were newlines in the last substitution. If there
1039 * are, marks on the previous line (without substitutions) will
1040 * also have to be changed.
1041 */
1042 if (cb->offset > 0) {
1043 for (k = cb->mark, l = cb->moffset; k < &cb->mark[26]; k++, l++)
1044 {
1045 if (*k == cb->lineno - 1 && *l == 0) {
1046 *l = *k + cb->offset;
1047 }
1048 }
1049 }
1050 }
1051 found = TRUE;
1052 cb->flags &= ~(CB_UPPER | CB_LOWER | CB_TUPPER | CB_TLOWER);
1053 size = cb->regexp->start - text - idx;
1054 if (size > 0) {
1055 /* copy first unchanged part of line to buffer */
1056 sub(cb, text + idx, size);
1057 }
1058 p = cb->replace;
1059 while (*p != '\0') {
1060 switch (*p) {
1061 case '&':
1062 /* insert matching string */
1063 sub(cb, cb->regexp->start, cb->regexp->size);
1064 break;
1065
1066 case '\\': /* special substitute characters */
1067 switch (*++p) {
1068 case '1':
1069 case '2':
1070 case '3':
1071 case '4':
1072 case '5':
1073 case '6':
1074 case '7':
1075 case '8':
1076 case '9':
1077 /* insert subexpression between \( \) */
1078 if (cb->regexp->se[*p - '1'].start != (char*) NULL) {
1079 sub(cb, cb->regexp->se[*p - '1'].start,
1080 cb->regexp->se[*p - '1'].size);
1081 break;
1082 }
1083 /* if no subexpression, fall though */
1084 default:
1085 sub(cb, p, 1); /* ignore preceding backslash */
1086 break;
1087
1088 case 'n':
1089 cb->buffer[cb->buflen++] = '\0';
1090 newlines++; /* insert newline */
1091 break;
1092
1093 case 'U':
1094 /* convert string to uppercase */
1095 cb->flags |= CB_UPPER;
1096 cb->flags &= ~(CB_LOWER | CB_TUPPER | CB_TLOWER);
1097 break;
1098
1099 case 'L':
1100 /* convert string to lowercase */
1101 cb->flags |= CB_LOWER;
1102 cb->flags &= ~(CB_UPPER | CB_TUPPER | CB_TLOWER);
1103 break;
1104
1105 case 'e':
1106 case 'E':
1107 /* end case conversion */
1108 cb->flags &= ~(CB_UPPER | CB_LOWER | CB_TUPPER | CB_TLOWER);
1109 break;
1110
1111 case 'u':
1112 /* convert char to uppercase */
1113 cb->flags |= CB_TUPPER;
1114 cb->flags &= ~CB_TLOWER;
1115 break;
1116
1117 case 'l':
1118 /* convert char to lowercase */
1119 cb->flags &= ~CB_TUPPER;
1120 cb->flags |= CB_TLOWER;
1121 break;
1122
1123 case '\0': /* sigh */
1124 continue;
1125 }
1126 break;
1127
1128 default: /* normal char */
1129 sub(cb, p, 1);
1130 break;
1131 }
1132 p++;
1133 }
1134
1135 idx = cb->regexp->start + cb->regexp->size - text;
1136 if (!(cb->flags & CB_GLOBSUBST) || text[idx] == '\0' ||
1137 (cb->regexp->size == 0 && text[++idx] == '\0')) {
1138 break;
1139 }
1140 }
1141
1142 if (found) {
1143 if (text[idx] != '\0') {
1144 /* concatenate unchanged part of line after found pattern */
1145 cb->flags &= ~(CB_UPPER | CB_LOWER | CB_TUPPER | CB_TLOWER);
1146 sub(cb, text + idx, strlen(text + idx));
1147 }
1148 if (!(cb->flags & CB_CURRENTBLK)) {
1149 /* start a new block of lines with substitutions in them */
1150 cb->flags |= CB_CHANGE;
1151 cb->first = cb->lineno;
1152 startblock(cb);
1153 cb->flags |= CB_CURRENTBLK;
1154 }
1155 /* add this changed line to block */
1156 cb->buffer[cb->buflen] = '\0';
1157 if (newlines == 0) {
1158 addblock(cb, cb->buffer);
1159 } else {
1160 /*
1161 * There were newlines in the substituted string. Add all
1162 * lines to the current block, and save the marks in range.
1163 */
1164 p = cb->buffer;
1165 do {
1166 addblock(cb, p);
1167 p += strlen(p) + 1;
1168 } while (p <= cb->buffer + cb->buflen);
1169
1170 for (k = cb->mark, l = cb->moffset; k < &cb->mark[26]; k++, l++) {
1171 if (*k == cb->lineno && *l == 0) {
1172 *l = *k + cb->offset;
1173 }
1174 }
1175 cb->offset += newlines;
1176 }
1177 cb->buflen = 0;
1178 cb->last = cb->lineno;
1179 } else {
1180 if (cb->flags & CB_SKIPPED) {
1181 /* two lines without substitutions now. Finish previous block. */
1182 endblock(cb);
1183 cb->lineno += cb->offset;
1184 cb->offset = 0;
1185 cb->flags &= ~(CB_CURRENTBLK | CB_SKIPPED);
1186 } else if (cb->flags & CB_CURRENTBLK) {
1187 /*
1188 * no substitution on this line, but there was one on the previous
1189 * line. mark this line as skipped, so it can still be added to
1190 * the block of changed lines if the next line has substitutions.
1191 */
1192 strcpy(cb->buffer, text);
1193 cb->flags |= CB_SKIPPED;
1194 }
1195 }
1196 cb->lineno++;
1197 }
1198
1199 /*
1200 * NAME: cmdbuf->substitute()
1201 * DESCRIPTION: do substitutions on a range of lines
1202 */
1203 int cb_subst(cb)
1204 register cmdbuf *cb;
1205 {
1206 char buf[MAX_LINE_SIZE], delim;
1207 Int m[26];
1208 Int edit;
1209 register char *p;
1210 register Int *k, *l;
1211
1212 delim = cb->cmd[0];
1213 if (delim == '\0' || strchr("0123456789gpl#-+", delim) != (char*) NULL) {
1214 /* no search pattern & replace string specified */
1215 if (cb->search[0] == '\0') {
1216 error("No previous substitute to repeat");
1217 }
1218 } else if (!isalpha(delim)) {
1219 register char *q;
1220
1221 /* get search pattern */
1222 p = pattern(cb->cmd + 1, delim, cb->search);
1223 /* get replace string */
1224 q = cb->replace;
1225 while (*p != '\0') {
1226 if (*p == delim) {
1227 p++;
1228 break;
1229 }
1230 if (q == cb->replace + STRINGSZ - 1) {
1231 cb->search[0] = '\0';
1232 error("Replace string too large");
1233 }
1234 if ((*q++ = *p++) == '\\' && *p != '\0') {
1235 *q++ = *p++;
1236 }
1237 }
1238 *q = '\0';
1239 cb->cmd = p;
1240 } else {
1241 /* cause error */
1242 cb->search[0] = '\0';
1243 }
1244
1245 if (cb->search[0] == '\0') {
1246 error("Missing regular expression for substitute");
1247 }
1248
1249 /* compile regexp */
1250 p = rx_comp(cb->regexp, cb->search);
1251 if (p != (char *) NULL) {
1252 error(p);
1253 }
1254
1255 cb_count(cb); /* get count */
1256 /* handle global flag */
1257 if (cb->cmd[0] == 'g') {
1258 cb->flags |= CB_GLOBSUBST;
1259 cb->cmd++;
1260 } else {
1261 cb->flags &= ~CB_GLOBSUBST;
1262 }
1263
1264 /* make a blank mark table */
1265 cb->moffset = m;
1266 for (l = m; l < &m[26]; ) {
1267 *l++ = 0;
1268 }
1269 cb->offset = 0;
1270
1271 /* do substitutions */
1272 cb_do(cb, cb->first);
1273 cb->lineno = cb->first;
1274 edit = cb->edit;
1275 cb->buffer = buf;
1276 cb->buflen = 0;
1277 cb->flags &= ~(CB_CURRENTBLK | CB_SKIPPED);
1278 eb_range(cb->edbuf, cb->first, cb->last, subst, (char *) cb, FALSE);
1279 if (cb->flags & CB_CURRENTBLK) {
1280 /* finish current block, if needed */
1281 endblock(cb);
1282 }
1283
1284 cb->othis = cb->uthis;
1285 if (edit == cb->edit) {
1286 error("Substitute pattern match failed");
1287 }
1288
1289 /* some marks may have been messed up. fix them */
1290 for (l = m, k = cb->mark; l < &m[26]; l++, k++) {
1291 if (*l != 0) {
1292 *k = *l;
1293 }
1294 }
1295
1296 return RET_FLAGS;
1297 }
1298
1299
1300 /*
1301 * NAME: getfname()
1302 * DESCRIPTION: copy a string to another buffer, unless it has length 0 or
1303 * is too long
1304 */
1305 static bool getfname(cb, buffer)
1306 register cmdbuf *cb;
1307 char *buffer;
1308 {
1309 register char *p, *q;
1310
1311 /* find the end of the filename */
1312 p = strchr(cb->cmd, ' ');
1313 q = strchr(cb->cmd, HT);
1314 if (q != (char *) NULL && (p == (char *) NULL || p > q)) {
1315 p = q;
1316 }
1317 q = strchr(cb->cmd, '|');
1318 if (q != (char *) NULL && (p == (char *) NULL || p > q)) {
1319 p = q;
1320 }
1321 if (p == (char *) NULL) {
1322 p = strchr(cb->cmd, '\0');
1323 }
1324
1325 /* checks */
1326 if (p == cb->cmd) {
1327 return FALSE;
1328 }
1329 if (p - cb->cmd >= STRINGSZ) {
1330 error("Filename too long");
1331 }
1332
1333 /* copy */
1334 memcpy(buffer, cb->cmd, p - cb->cmd);
1335 buffer[p - cb->cmd] = '\0';
1336 cb->cmd = p;
1337 return TRUE;
1338 }
1339
1340 /*
1341 * NAME: cmdbuf->file()
1342 * DESCRIPTION: get/set the file name & current line, etc.
1343 */
1344 int cb_file(cb)
1345 register cmdbuf *cb;
1346 {
1347 not_in_global(cb);
1348
1349 if (getfname(cb, cb->fname)) {
1350 /* file name is changed: mark the file as "not edited" */
1351 cb->flags |= CB_NOIMAGE;
1352 }
1353
1354 /* give statistics */
1355 if (cb->fname[0] == '\0') {
1356 output("No file");
1357 } else {
1358 output("\"%s\"", cb->fname);
1359 }
1360 if (cb->flags & CB_NOIMAGE) {
1361 output(" [Not edited]");
1362 }
1363 if (cb->edit > 0) {
1364 output(" [Modified]");
1365 }
1366 output(" line %ld of %ld --%d%%--\012", /* LF */
1367 (long) cb->this, (long) cb->edbuf->lines,
1368 (cb->edbuf->lines == 0) ? 0 :
1369 (int) ((100 * cb->this) / cb->edbuf->lines));
1370
1371 return 0;
1372 }
1373
1374 /*
1375 * NAME: io->show()
1376 * DESCRIPTION: show statistics on the file just read/written
1377 */
1378 static void io_show(iob)
1379 register io *iob;
1380 {
1381 output("%ld lines, %ld characters", (long) iob->lines,
1382 (long) (iob->chars + iob->zero - iob->split - iob->ill));
1383 if (iob->zero > 0) {
1384 output(" [%ld zero]", (long) iob->zero);
1385 }
1386 if (iob->split > 0) {
1387 output(" [%ld split]", (long) iob->split);
1388 }
1389 if (iob->ill) {
1390 output(" [incomplete last line]");
1391 }
1392 output("\012"); /* LF */
1393 }
1394
1395 /*
1396 * NAME: cmdbuf->read()
1397 * DESCRIPTION: insert a file in the current edit buffer
1398 */
1399 int cb_read(cb)
1400 register cmdbuf *cb;
1401 {
1402 char buffer[STRINGSZ];
1403 io iob;
1404
1405 not_in_global(cb);
1406
1407 if (!getfname(cb, buffer)) {
1408 if (cb->fname[0] == '\0') {
1409 error("No current filename");
1410 }
1411 /* read current file, by default. I don't know why, but ex has it
1412 that way. */
1413 strcpy(buffer, cb->fname);
1414 }
1415
1416 cb_do(cb, cb->first);
1417 output("\"%s\" ", buffer);
1418 if (!io_load(cb->edbuf, buffer, cb->first, &iob)) {
1419 error("is unreadable");
1420 }
1421 io_show(&iob);
1422
1423 cb->edit++;
1424 cb->this = cb->first + iob.lines;
1425
1426 return 0;
1427 }
1428
1429 /*
1430 * NAME: cmdbuf->edit()
1431 * DESCRIPTION: edit a new file
1432 */
1433 int cb_edit(cb)
1434 register cmdbuf *cb;
1435 {
1436 io iob;
1437
1438 not_in_global(cb);
1439
1440 if (cb->edit > 0 && !(cb->flags & CB_EXCL)) {
1441 error("No write since last change (edit! overrides)");
1442 }
1443
1444 getfname(cb, cb->fname);
1445 if (cb->fname[0] == '\0') {
1446 error("No current filename");
1447 }
1448
1449 m_static();
1450 eb_clear(cb->edbuf);
1451 m_dynamic();
1452 cb->flags &= ~CB_NOIMAGE;
1453 cb->edit = 0;
1454 cb->first = cb->this = 0;
1455 memset(cb->mark, '\0', sizeof(cb->mark));
1456 cb->buf = 0;
1457 memset(cb->zbuf, '\0', sizeof(cb->zbuf));
1458 cb->undo = (block) -1; /* not 0! */
1459
1460 output("\"%s\" ", cb->fname);
1461 if (!io_load(cb->edbuf, cb->fname, cb->first, &iob)) {
1462 error("is unreadable");
1463 }
1464 io_show(&iob);
1465 if (iob.zero > 0 || iob.split > 0 || iob.ill) {
1466 /* the editbuffer in memory is not a perfect image of the file read */
1467 cb->flags |= CB_NOIMAGE;
1468 }
1469
1470 cb->this = iob.lines;
1471
1472 return 0;
1473 }
1474
1475 /*
1476 * NAME: cmdbuf->quit()
1477 * DESCRIPTION: quit editing
1478 */
1479 int cb_quit(cb)
1480 cmdbuf *cb;
1481 {
1482 not_in_global(cb);
1483
1484 if (cb->edit > 0 && !(cb->flags & CB_EXCL)) {
1485 error("No write since last change (quit! overrides)");
1486 }
1487
1488 return RET_QUIT;
1489 }
1490
1491 /*
1492 * NAME: cmdbuf->write()
1493 * DESCRIPTION: write a range of lines to a file
1494 */
1495 int cb_write(cb)
1496 register cmdbuf *cb;
1497 {
1498 char buffer[STRINGSZ];
1499 bool append;
1500 io iob;
1501
1502 not_in_global(cb);
1503
1504 if (strncmp(cb->cmd, ">>", 2) == 0) {
1505 append = TRUE;
1506 cb->cmd = skipst(cb->cmd + 2);
1507 } else {
1508 append = FALSE;
1509 }
1510
1511 /* check if write can be done */
1512 if (!getfname(cb, buffer)) {
1513 if (cb->fname[0] == '\0') {
1514 error("No current filename");
1515 }
1516 strcpy(buffer, cb->fname);
1517 }
1518 if (strcmp(buffer, cb->fname) == 0) {
1519 if (cb->first == 1 && cb->last == cb->edbuf->lines) {
1520 if ((cb->flags & (CB_NOIMAGE|CB_EXCL)) == CB_NOIMAGE) {
1521 error("File is changed (use w! to override)");
1522 }
1523 } else if (!(cb->flags & CB_EXCL)) {
1524 error("Use w! to write partial buffer");
1525 }
1526 }
1527
1528 output("\"%s\" ", buffer);
1529 if (!io_save(cb->edbuf, buffer, cb->first, cb->last, append, &iob)) {
1530 error("write failed");
1531 }
1532 io_show(&iob);
1533
1534 if (cb->first == 1 && cb->last == cb->edbuf->lines) {
1535 /* file is now perfect image of editbuffer in memory */
1536 cb->flags &= ~CB_NOIMAGE;
1537 cb->edit = 0;
1538 }
1539
1540 return 0;
1541 }
1542
1543 /*
1544 * NAME: cmdbuf->wq()
1545 * DESCRIPTION: write a range of lines to a file and quit
1546 */
1547 int cb_wq(cb)
1548 cmdbuf *cb;
1549 {
1550 cb->first = 1;
1551 cb->last = cb->edbuf->lines;
1552 cb_write(cb);
1553 return cb_quit(cb);
1554 }
1555
1556 /*
1557 * NAME: cmdbuf->xit()
1558 * DESCRIPTION: write to the current file if modified, and quit
1559 */
1560 int cb_xit(cb)
1561 register cmdbuf *cb;
1562 {
1563 if (cb->edit > 0) {
1564 cb->flags |= CB_EXCL;
1565 return cb_wq(cb);
1566 } else {
1567 not_in_global(cb);
1568
1569 return RET_QUIT;
1570 }
1571 }
1572
1573
1574 /*
1575 * NAME: cmdbuf->set()
1576 * DESCRIPTION: get/set variable(s)
1577 */
1578 int cb_set(cb)
1579 register cmdbuf *cb;
1580 {
1581 char buffer[STRINGSZ];
1582 register char *p, *q;
1583
1584 not_in_global(cb);
1585
1586 p = cb->cmd;
1587 if (strlen(p) >= STRINGSZ) {
1588 p[STRINGSZ - 1] = '\0'; /* must fit in the buffer */
1589 }
1590 if (*p == '\0') {
1591 /* no arguments */
1592 va_show(cb->vars);
1593 } else {
1594 do {
1595 /* copy argument */
1596 q = buffer;
1597 while (*p != '\0' && *p != ' ' && *p != HT) {
1598 *q++ = *p++;
1599 }
1600 *q = '\0';
1601 /* let va_set() process it */
1602 va_set(cb->vars, buffer);
1603 p = skipst(p);
1604 } while (*p != '\0');
1605 cb->cmd = p;
1606 }
1607 return 0;
1608 }
1609
This page was automatically generated by the
LXR engine.
Visit the LXR main site for more
information.