diff options
Diffstat (limited to 'editors')
-rw-r--r-- | editors/Config.in | 8 | ||||
-rw-r--r-- | editors/Makefile.in | 5 | ||||
-rw-r--r-- | editors/ed.c | 1363 |
3 files changed, 1374 insertions, 2 deletions
diff --git a/editors/Config.in b/editors/Config.in index a30879c63..14c316c08 100644 --- a/editors/Config.in +++ b/editors/Config.in | |||
@@ -20,6 +20,14 @@ config CONFIG_FEATURE_AWK_MATH | |||
20 | Enable math functions of the Awk programming language. | 20 | Enable math functions of the Awk programming language. |
21 | NOTE: This will require libm to be present for linking. | 21 | NOTE: This will require libm to be present for linking. |
22 | 22 | ||
23 | config CONFIG_ED | ||
24 | bool "ed" | ||
25 | default n | ||
26 | help | ||
27 | The original 1970's Unix text editor, from the days of teletypes. | ||
28 | Small, simple, evil. Part of SUSv3. If you're not already using | ||
29 | this, you don't need it. | ||
30 | |||
23 | config CONFIG_PATCH | 31 | config CONFIG_PATCH |
24 | bool "patch" | 32 | bool "patch" |
25 | default n | 33 | default n |
diff --git a/editors/Makefile.in b/editors/Makefile.in index 805017dcc..9a46e32c2 100644 --- a/editors/Makefile.in +++ b/editors/Makefile.in | |||
@@ -11,8 +11,9 @@ endif | |||
11 | srcdir=$(top_srcdir)/editors | 11 | srcdir=$(top_srcdir)/editors |
12 | 12 | ||
13 | EDITOR-y:= | 13 | EDITOR-y:= |
14 | EDITOR-$(CONFIG_AWK) += awk.o | 14 | EDITOR-$(CONFIG_AWK) += awk.o |
15 | EDITOR-$(CONFIG_PATCH) += patch.o | 15 | EDITOR-$(CONFIG_ED) += ed.o |
16 | EDITOR-$(CONFIG_PATCH) += patch.o | ||
16 | EDITOR-$(CONFIG_SED) += sed.o | 17 | EDITOR-$(CONFIG_SED) += sed.o |
17 | EDITOR-$(CONFIG_VI) += vi.o | 18 | EDITOR-$(CONFIG_VI) += vi.o |
18 | 19 | ||
diff --git a/editors/ed.c b/editors/ed.c new file mode 100644 index 000000000..0414bfc9d --- /dev/null +++ b/editors/ed.c | |||
@@ -0,0 +1,1363 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2002 by David I. Bell | ||
3 | * Permission is granted to use, distribute, or modify this source, | ||
4 | * provided that this copyright notice remains intact. | ||
5 | * | ||
6 | * The "ed" built-in command (much simplified) | ||
7 | */ | ||
8 | |||
9 | #include <stdio.h> | ||
10 | #include <stdlib.h> | ||
11 | #include <unistd.h> | ||
12 | #include <fcntl.h> | ||
13 | #include <string.h> | ||
14 | #include <memory.h> | ||
15 | #include <time.h> | ||
16 | #include <ctype.h> | ||
17 | #include <sys/param.h> | ||
18 | #include <malloc.h> | ||
19 | #include "busybox.h" | ||
20 | |||
21 | #define USERSIZE 1024 /* max line length typed in by user */ | ||
22 | #define INITBUF_SIZE 1024 /* initial buffer size */ | ||
23 | typedef struct LINE { | ||
24 | struct LINE *next; | ||
25 | struct LINE *prev; | ||
26 | int len; | ||
27 | char data[1]; | ||
28 | } LINE; | ||
29 | |||
30 | static LINE lines, *curLine; | ||
31 | static int curNum, lastNum, marks[26], dirty; | ||
32 | static char *bufBase, *bufPtr, *fileName, searchString[USERSIZE]; | ||
33 | static int bufUsed, bufSize; | ||
34 | |||
35 | static void doCommands(void); | ||
36 | static void subCommand(const char *cmd, int num1, int num2); | ||
37 | static int getNum(const char **retcp, int *retHaveNum, int *retNum); | ||
38 | static int setCurNum(int num); | ||
39 | static int initEdit(void); | ||
40 | static void termEdit(void); | ||
41 | static void addLines(int num); | ||
42 | static int insertLine(int num, const char *data, int len); | ||
43 | static int deleteLines(int num1, int num2); | ||
44 | static int printLines(int num1, int num2, int expandFlag); | ||
45 | static int writeLines(const char *file, int num1, int num2); | ||
46 | static int readLines(const char *file, int num); | ||
47 | static int searchLines(const char *str, int num1, int num2); | ||
48 | static LINE *findLine(int num); | ||
49 | |||
50 | static int findString(const LINE *lp, const char * str, int len, int offset); | ||
51 | |||
52 | int ed_main(int argc, char **argv) | ||
53 | { | ||
54 | if (!initEdit()) | ||
55 | return EXIT_FAILURE; | ||
56 | |||
57 | if (argc > 1) { | ||
58 | fileName = strdup(argv[1]); | ||
59 | |||
60 | if (fileName == NULL) { | ||
61 | bb_error_msg("No memory"); | ||
62 | termEdit(); | ||
63 | return EXIT_SUCCESS; | ||
64 | } | ||
65 | |||
66 | if (!readLines(fileName, 1)) { | ||
67 | termEdit(); | ||
68 | return EXIT_SUCCESS; | ||
69 | } | ||
70 | |||
71 | if (lastNum) | ||
72 | setCurNum(1); | ||
73 | |||
74 | dirty = FALSE; | ||
75 | } | ||
76 | |||
77 | doCommands(); | ||
78 | |||
79 | termEdit(); | ||
80 | return EXIT_SUCCESS; | ||
81 | } | ||
82 | |||
83 | /* | ||
84 | * Read commands until we are told to stop. | ||
85 | */ | ||
86 | static void doCommands(void) | ||
87 | { | ||
88 | const char *cp; | ||
89 | char *endbuf, *newname, buf[USERSIZE]; | ||
90 | int len, num1, num2, have1, have2; | ||
91 | |||
92 | while (TRUE) | ||
93 | { | ||
94 | printf(": "); | ||
95 | fflush(stdout); | ||
96 | |||
97 | if (fgets(buf, sizeof(buf), stdin) == NULL) | ||
98 | return; | ||
99 | |||
100 | len = strlen(buf); | ||
101 | |||
102 | if (len == 0) | ||
103 | return; | ||
104 | |||
105 | endbuf = &buf[len - 1]; | ||
106 | |||
107 | if (*endbuf != '\n') | ||
108 | { | ||
109 | bb_error_msg("Command line too long"); | ||
110 | |||
111 | do | ||
112 | { | ||
113 | len = fgetc(stdin); | ||
114 | } | ||
115 | while ((len != EOF) && (len != '\n')); | ||
116 | |||
117 | continue; | ||
118 | } | ||
119 | |||
120 | while ((endbuf > buf) && isblank(endbuf[-1])) | ||
121 | endbuf--; | ||
122 | |||
123 | *endbuf = '\0'; | ||
124 | |||
125 | cp = buf; | ||
126 | |||
127 | while (isblank(*cp)) | ||
128 | cp++; | ||
129 | |||
130 | have1 = FALSE; | ||
131 | have2 = FALSE; | ||
132 | |||
133 | if ((curNum == 0) && (lastNum > 0)) | ||
134 | { | ||
135 | curNum = 1; | ||
136 | curLine = lines.next; | ||
137 | } | ||
138 | |||
139 | if (!getNum(&cp, &have1, &num1)) | ||
140 | continue; | ||
141 | |||
142 | while (isblank(*cp)) | ||
143 | cp++; | ||
144 | |||
145 | if (*cp == ',') | ||
146 | { | ||
147 | cp++; | ||
148 | |||
149 | if (!getNum(&cp, &have2, &num2)) | ||
150 | continue; | ||
151 | |||
152 | if (!have1) | ||
153 | num1 = 1; | ||
154 | |||
155 | if (!have2) | ||
156 | num2 = lastNum; | ||
157 | |||
158 | have1 = TRUE; | ||
159 | have2 = TRUE; | ||
160 | } | ||
161 | |||
162 | if (!have1) | ||
163 | num1 = curNum; | ||
164 | |||
165 | if (!have2) | ||
166 | num2 = num1; | ||
167 | |||
168 | switch (*cp++) | ||
169 | { | ||
170 | case 'a': | ||
171 | addLines(num1 + 1); | ||
172 | break; | ||
173 | |||
174 | case 'c': | ||
175 | deleteLines(num1, num2); | ||
176 | addLines(num1); | ||
177 | break; | ||
178 | |||
179 | case 'd': | ||
180 | deleteLines(num1, num2); | ||
181 | break; | ||
182 | |||
183 | case 'f': | ||
184 | if (*cp && !isblank(*cp)) | ||
185 | { | ||
186 | bb_error_msg("Bad file command"); | ||
187 | break; | ||
188 | } | ||
189 | |||
190 | while (isblank(*cp)) | ||
191 | cp++; | ||
192 | |||
193 | if (*cp == '\0') | ||
194 | { | ||
195 | if (fileName) | ||
196 | printf("\"%s\"\n", fileName); | ||
197 | else | ||
198 | printf("No file name\n"); | ||
199 | |||
200 | break; | ||
201 | } | ||
202 | |||
203 | newname = strdup(cp); | ||
204 | |||
205 | if (newname == NULL) | ||
206 | { | ||
207 | bb_error_msg("No memory for file name"); | ||
208 | break; | ||
209 | } | ||
210 | |||
211 | if (fileName) | ||
212 | free(fileName); | ||
213 | |||
214 | fileName = newname; | ||
215 | break; | ||
216 | |||
217 | case 'i': | ||
218 | addLines(num1); | ||
219 | break; | ||
220 | |||
221 | case 'k': | ||
222 | while (isblank(*cp)) | ||
223 | cp++; | ||
224 | |||
225 | if ((*cp < 'a') || (*cp > 'a') || cp[1]) | ||
226 | { | ||
227 | bb_error_msg("Bad mark name"); | ||
228 | break; | ||
229 | } | ||
230 | |||
231 | marks[*cp - 'a'] = num2; | ||
232 | break; | ||
233 | |||
234 | case 'l': | ||
235 | printLines(num1, num2, TRUE); | ||
236 | break; | ||
237 | |||
238 | case 'p': | ||
239 | printLines(num1, num2, FALSE); | ||
240 | break; | ||
241 | |||
242 | case 'q': | ||
243 | while (isblank(*cp)) | ||
244 | cp++; | ||
245 | |||
246 | if (have1 || *cp) | ||
247 | { | ||
248 | bb_error_msg("Bad quit command"); | ||
249 | break; | ||
250 | } | ||
251 | |||
252 | if (!dirty) | ||
253 | return; | ||
254 | |||
255 | printf("Really quit? "); | ||
256 | fflush(stdout); | ||
257 | |||
258 | buf[0] = '\0'; | ||
259 | fgets(buf, sizeof(buf), stdin); | ||
260 | cp = buf; | ||
261 | |||
262 | while (isblank(*cp)) | ||
263 | cp++; | ||
264 | |||
265 | if ((*cp == 'y') || (*cp == 'Y')) | ||
266 | return; | ||
267 | |||
268 | break; | ||
269 | |||
270 | case 'r': | ||
271 | if (*cp && !isblank(*cp)) | ||
272 | { | ||
273 | bb_error_msg("Bad read command"); | ||
274 | break; | ||
275 | } | ||
276 | |||
277 | while (isblank(*cp)) | ||
278 | cp++; | ||
279 | |||
280 | if (*cp == '\0') | ||
281 | { | ||
282 | bb_error_msg("No file name"); | ||
283 | break; | ||
284 | } | ||
285 | |||
286 | if (!have1) | ||
287 | num1 = lastNum; | ||
288 | |||
289 | if (readLines(cp, num1 + 1)) | ||
290 | break; | ||
291 | |||
292 | if (fileName == NULL) | ||
293 | fileName = strdup(cp); | ||
294 | |||
295 | break; | ||
296 | |||
297 | case 's': | ||
298 | subCommand(cp, num1, num2); | ||
299 | break; | ||
300 | |||
301 | case 'w': | ||
302 | if (*cp && !isblank(*cp)) | ||
303 | { | ||
304 | bb_error_msg("Bad write command"); | ||
305 | break; | ||
306 | } | ||
307 | |||
308 | while (isblank(*cp)) | ||
309 | cp++; | ||
310 | |||
311 | if (!have1) { | ||
312 | num1 = 1; | ||
313 | num2 = lastNum; | ||
314 | } | ||
315 | |||
316 | if (*cp == '\0') | ||
317 | cp = fileName; | ||
318 | |||
319 | if (cp == NULL) | ||
320 | { | ||
321 | bb_error_msg("No file name specified"); | ||
322 | break; | ||
323 | } | ||
324 | |||
325 | writeLines(cp, num1, num2); | ||
326 | break; | ||
327 | |||
328 | case 'z': | ||
329 | switch (*cp) | ||
330 | { | ||
331 | case '-': | ||
332 | printLines(curNum-21, curNum, FALSE); | ||
333 | break; | ||
334 | case '.': | ||
335 | printLines(curNum-11, curNum+10, FALSE); | ||
336 | break; | ||
337 | default: | ||
338 | printLines(curNum, curNum+21, FALSE); | ||
339 | break; | ||
340 | } | ||
341 | break; | ||
342 | |||
343 | case '.': | ||
344 | if (have1) | ||
345 | { | ||
346 | bb_error_msg("No arguments allowed"); | ||
347 | break; | ||
348 | } | ||
349 | |||
350 | printLines(curNum, curNum, FALSE); | ||
351 | break; | ||
352 | |||
353 | case '-': | ||
354 | if (setCurNum(curNum - 1)) | ||
355 | printLines(curNum, curNum, FALSE); | ||
356 | |||
357 | break; | ||
358 | |||
359 | case '=': | ||
360 | printf("%d\n", num1); | ||
361 | break; | ||
362 | |||
363 | case '\0': | ||
364 | if (have1) | ||
365 | { | ||
366 | printLines(num2, num2, FALSE); | ||
367 | break; | ||
368 | } | ||
369 | |||
370 | if (setCurNum(curNum + 1)) | ||
371 | printLines(curNum, curNum, FALSE); | ||
372 | |||
373 | break; | ||
374 | |||
375 | default: | ||
376 | bb_error_msg("Unimplemented command"); | ||
377 | break; | ||
378 | } | ||
379 | } | ||
380 | } | ||
381 | |||
382 | |||
383 | /* | ||
384 | * Do the substitute command. | ||
385 | * The current line is set to the last substitution done. | ||
386 | */ | ||
387 | static void subCommand(const char * cmd, int num1, int num2) | ||
388 | { | ||
389 | char *cp, *oldStr, *newStr, buf[USERSIZE]; | ||
390 | int delim, oldLen, newLen, deltaLen, offset; | ||
391 | LINE *lp, *nlp; | ||
392 | int globalFlag, printFlag, didSub, needPrint; | ||
393 | |||
394 | if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) | ||
395 | { | ||
396 | bb_error_msg("Bad line range for substitute"); | ||
397 | |||
398 | return; | ||
399 | } | ||
400 | |||
401 | globalFlag = FALSE; | ||
402 | printFlag = FALSE; | ||
403 | didSub = FALSE; | ||
404 | needPrint = FALSE; | ||
405 | |||
406 | /* | ||
407 | * Copy the command so we can modify it. | ||
408 | */ | ||
409 | strcpy(buf, cmd); | ||
410 | cp = buf; | ||
411 | |||
412 | if (isblank(*cp) || (*cp == '\0')) | ||
413 | { | ||
414 | bb_error_msg("Bad delimiter for substitute"); | ||
415 | |||
416 | return; | ||
417 | } | ||
418 | |||
419 | delim = *cp++; | ||
420 | oldStr = cp; | ||
421 | |||
422 | cp = strchr(cp, delim); | ||
423 | |||
424 | if (cp == NULL) | ||
425 | { | ||
426 | bb_error_msg("Missing 2nd delimiter for substitute"); | ||
427 | |||
428 | return; | ||
429 | } | ||
430 | |||
431 | *cp++ = '\0'; | ||
432 | |||
433 | newStr = cp; | ||
434 | cp = strchr(cp, delim); | ||
435 | |||
436 | if (cp) | ||
437 | *cp++ = '\0'; | ||
438 | else | ||
439 | cp = ""; | ||
440 | |||
441 | while (*cp) switch (*cp++) | ||
442 | { | ||
443 | case 'g': | ||
444 | globalFlag = TRUE; | ||
445 | break; | ||
446 | |||
447 | case 'p': | ||
448 | printFlag = TRUE; | ||
449 | break; | ||
450 | |||
451 | default: | ||
452 | bb_error_msg("Unknown option for substitute"); | ||
453 | |||
454 | return; | ||
455 | } | ||
456 | |||
457 | if (*oldStr == '\0') | ||
458 | { | ||
459 | if (searchString[0] == '\0') | ||
460 | { | ||
461 | bb_error_msg("No previous search string"); | ||
462 | |||
463 | return; | ||
464 | } | ||
465 | |||
466 | oldStr = searchString; | ||
467 | } | ||
468 | |||
469 | if (oldStr != searchString) | ||
470 | strcpy(searchString, oldStr); | ||
471 | |||
472 | lp = findLine(num1); | ||
473 | |||
474 | if (lp == NULL) | ||
475 | return; | ||
476 | |||
477 | oldLen = strlen(oldStr); | ||
478 | newLen = strlen(newStr); | ||
479 | deltaLen = newLen - oldLen; | ||
480 | offset = 0; | ||
481 | nlp = NULL; | ||
482 | |||
483 | while (num1 <= num2) | ||
484 | { | ||
485 | offset = findString(lp, oldStr, oldLen, offset); | ||
486 | |||
487 | if (offset < 0) | ||
488 | { | ||
489 | if (needPrint) | ||
490 | { | ||
491 | printLines(num1, num1, FALSE); | ||
492 | needPrint = FALSE; | ||
493 | } | ||
494 | |||
495 | offset = 0; | ||
496 | lp = lp->next; | ||
497 | num1++; | ||
498 | |||
499 | continue; | ||
500 | } | ||
501 | |||
502 | needPrint = printFlag; | ||
503 | didSub = TRUE; | ||
504 | dirty = TRUE; | ||
505 | |||
506 | /* | ||
507 | * If the replacement string is the same size or shorter | ||
508 | * than the old string, then the substitution is easy. | ||
509 | */ | ||
510 | if (deltaLen <= 0) | ||
511 | { | ||
512 | memcpy(&lp->data[offset], newStr, newLen); | ||
513 | |||
514 | if (deltaLen) | ||
515 | { | ||
516 | memcpy(&lp->data[offset + newLen], | ||
517 | &lp->data[offset + oldLen], | ||
518 | lp->len - offset - oldLen); | ||
519 | |||
520 | lp->len += deltaLen; | ||
521 | } | ||
522 | |||
523 | offset += newLen; | ||
524 | |||
525 | if (globalFlag) | ||
526 | continue; | ||
527 | |||
528 | if (needPrint) | ||
529 | { | ||
530 | printLines(num1, num1, FALSE); | ||
531 | needPrint = FALSE; | ||
532 | } | ||
533 | |||
534 | lp = lp->next; | ||
535 | num1++; | ||
536 | |||
537 | continue; | ||
538 | } | ||
539 | |||
540 | /* | ||
541 | * The new string is larger, so allocate a new line | ||
542 | * structure and use that. Link it in in place of | ||
543 | * the old line structure. | ||
544 | */ | ||
545 | nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen); | ||
546 | |||
547 | if (nlp == NULL) | ||
548 | { | ||
549 | bb_error_msg("Cannot get memory for line"); | ||
550 | |||
551 | return; | ||
552 | } | ||
553 | |||
554 | nlp->len = lp->len + deltaLen; | ||
555 | |||
556 | memcpy(nlp->data, lp->data, offset); | ||
557 | |||
558 | memcpy(&nlp->data[offset], newStr, newLen); | ||
559 | |||
560 | memcpy(&nlp->data[offset + newLen], | ||
561 | &lp->data[offset + oldLen], | ||
562 | lp->len - offset - oldLen); | ||
563 | |||
564 | nlp->next = lp->next; | ||
565 | nlp->prev = lp->prev; | ||
566 | nlp->prev->next = nlp; | ||
567 | nlp->next->prev = nlp; | ||
568 | |||
569 | if (curLine == lp) | ||
570 | curLine = nlp; | ||
571 | |||
572 | free(lp); | ||
573 | lp = nlp; | ||
574 | |||
575 | offset += newLen; | ||
576 | |||
577 | if (globalFlag) | ||
578 | continue; | ||
579 | |||
580 | if (needPrint) | ||
581 | { | ||
582 | printLines(num1, num1, FALSE); | ||
583 | needPrint = FALSE; | ||
584 | } | ||
585 | |||
586 | lp = lp->next; | ||
587 | num1++; | ||
588 | } | ||
589 | |||
590 | if (!didSub) | ||
591 | bb_error_msg("No substitutions found for \"%s\"", oldStr); | ||
592 | } | ||
593 | |||
594 | |||
595 | /* | ||
596 | * Search a line for the specified string starting at the specified | ||
597 | * offset in the line. Returns the offset of the found string, or -1. | ||
598 | */ | ||
599 | static int findString( const LINE * lp, const char * str, int len, int offset) | ||
600 | { | ||
601 | int left; | ||
602 | const char *cp, *ncp; | ||
603 | |||
604 | cp = &lp->data[offset]; | ||
605 | left = lp->len - offset; | ||
606 | |||
607 | while (left >= len) | ||
608 | { | ||
609 | ncp = memchr(cp, *str, left); | ||
610 | |||
611 | if (ncp == NULL) | ||
612 | return -1; | ||
613 | |||
614 | left -= (ncp - cp); | ||
615 | |||
616 | if (left < len) | ||
617 | return -1; | ||
618 | |||
619 | cp = ncp; | ||
620 | |||
621 | if (memcmp(cp, str, len) == 0) | ||
622 | return (cp - lp->data); | ||
623 | |||
624 | cp++; | ||
625 | left--; | ||
626 | } | ||
627 | |||
628 | return -1; | ||
629 | } | ||
630 | |||
631 | |||
632 | /* | ||
633 | * Add lines which are typed in by the user. | ||
634 | * The lines are inserted just before the specified line number. | ||
635 | * The lines are terminated by a line containing a single dot (ugly!), | ||
636 | * or by an end of file. | ||
637 | */ | ||
638 | static void addLines(int num) | ||
639 | { | ||
640 | int len; | ||
641 | char buf[USERSIZE + 1]; | ||
642 | |||
643 | while (fgets(buf, sizeof(buf), stdin)) | ||
644 | { | ||
645 | if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0')) | ||
646 | return; | ||
647 | |||
648 | len = strlen(buf); | ||
649 | |||
650 | if (len == 0) | ||
651 | return; | ||
652 | |||
653 | if (buf[len - 1] != '\n') | ||
654 | { | ||
655 | bb_error_msg("Line too long"); | ||
656 | |||
657 | do | ||
658 | { | ||
659 | len = fgetc(stdin); | ||
660 | } | ||
661 | while ((len != EOF) && (len != '\n')); | ||
662 | |||
663 | return; | ||
664 | } | ||
665 | |||
666 | if (!insertLine(num++, buf, len)) | ||
667 | return; | ||
668 | } | ||
669 | } | ||
670 | |||
671 | |||
672 | /* | ||
673 | * Parse a line number argument if it is present. This is a sum | ||
674 | * or difference of numbers, '.', '$', 'x, or a search string. | ||
675 | * Returns TRUE if successful (whether or not there was a number). | ||
676 | * Returns FALSE if there was a parsing error, with a message output. | ||
677 | * Whether there was a number is returned indirectly, as is the number. | ||
678 | * The character pointer which stopped the scan is also returned. | ||
679 | */ | ||
680 | static int getNum(const char **retcp, int *retHaveNum, int *retNum) | ||
681 | { | ||
682 | const char *cp; | ||
683 | char *endStr, str[USERSIZE]; | ||
684 | int haveNum, value, num, sign; | ||
685 | |||
686 | cp = *retcp; | ||
687 | haveNum = FALSE; | ||
688 | value = 0; | ||
689 | sign = 1; | ||
690 | |||
691 | while (TRUE) | ||
692 | { | ||
693 | while (isblank(*cp)) | ||
694 | cp++; | ||
695 | |||
696 | switch (*cp) | ||
697 | { | ||
698 | case '.': | ||
699 | haveNum = TRUE; | ||
700 | num = curNum; | ||
701 | cp++; | ||
702 | break; | ||
703 | |||
704 | case '$': | ||
705 | haveNum = TRUE; | ||
706 | num = lastNum; | ||
707 | cp++; | ||
708 | break; | ||
709 | |||
710 | case '\'': | ||
711 | cp++; | ||
712 | |||
713 | if ((*cp < 'a') || (*cp > 'z')) | ||
714 | { | ||
715 | bb_error_msg("Bad mark name"); | ||
716 | |||
717 | return FALSE; | ||
718 | } | ||
719 | |||
720 | haveNum = TRUE; | ||
721 | num = marks[*cp++ - 'a']; | ||
722 | break; | ||
723 | |||
724 | case '/': | ||
725 | strcpy(str, ++cp); | ||
726 | endStr = strchr(str, '/'); | ||
727 | |||
728 | if (endStr) | ||
729 | { | ||
730 | *endStr++ = '\0'; | ||
731 | cp += (endStr - str); | ||
732 | } | ||
733 | else | ||
734 | cp = ""; | ||
735 | |||
736 | num = searchLines(str, curNum, lastNum); | ||
737 | |||
738 | if (num == 0) | ||
739 | return FALSE; | ||
740 | |||
741 | haveNum = TRUE; | ||
742 | break; | ||
743 | |||
744 | default: | ||
745 | if (!isdigit(*cp)) | ||
746 | { | ||
747 | *retcp = cp; | ||
748 | *retHaveNum = haveNum; | ||
749 | *retNum = value; | ||
750 | |||
751 | return TRUE; | ||
752 | } | ||
753 | |||
754 | num = 0; | ||
755 | |||
756 | while (isdigit(*cp)) | ||
757 | num = num * 10 + *cp++ - '0'; | ||
758 | |||
759 | haveNum = TRUE; | ||
760 | break; | ||
761 | } | ||
762 | |||
763 | value += num * sign; | ||
764 | |||
765 | while (isblank(*cp)) | ||
766 | cp++; | ||
767 | |||
768 | switch (*cp) | ||
769 | { | ||
770 | case '-': | ||
771 | sign = -1; | ||
772 | cp++; | ||
773 | break; | ||
774 | |||
775 | case '+': | ||
776 | sign = 1; | ||
777 | cp++; | ||
778 | break; | ||
779 | |||
780 | default: | ||
781 | *retcp = cp; | ||
782 | *retHaveNum = haveNum; | ||
783 | *retNum = value; | ||
784 | |||
785 | return TRUE; | ||
786 | } | ||
787 | } | ||
788 | } | ||
789 | |||
790 | |||
791 | /* | ||
792 | * Initialize everything for editing. | ||
793 | */ | ||
794 | static int initEdit(void) | ||
795 | { | ||
796 | int i; | ||
797 | |||
798 | bufSize = INITBUF_SIZE; | ||
799 | bufBase = malloc(bufSize); | ||
800 | |||
801 | if (bufBase == NULL) | ||
802 | { | ||
803 | bb_error_msg("No memory for buffer"); | ||
804 | |||
805 | return FALSE; | ||
806 | } | ||
807 | |||
808 | bufPtr = bufBase; | ||
809 | bufUsed = 0; | ||
810 | |||
811 | lines.next = &lines; | ||
812 | lines.prev = &lines; | ||
813 | |||
814 | curLine = NULL; | ||
815 | curNum = 0; | ||
816 | lastNum = 0; | ||
817 | dirty = FALSE; | ||
818 | fileName = NULL; | ||
819 | searchString[0] = '\0'; | ||
820 | |||
821 | for (i = 0; i < 26; i++) | ||
822 | marks[i] = 0; | ||
823 | |||
824 | return TRUE; | ||
825 | } | ||
826 | |||
827 | |||
828 | /* | ||
829 | * Finish editing. | ||
830 | */ | ||
831 | static void termEdit(void) | ||
832 | { | ||
833 | if (bufBase) | ||
834 | free(bufBase); | ||
835 | |||
836 | bufBase = NULL; | ||
837 | bufPtr = NULL; | ||
838 | bufSize = 0; | ||
839 | bufUsed = 0; | ||
840 | |||
841 | if (fileName) | ||
842 | free(fileName); | ||
843 | |||
844 | fileName = NULL; | ||
845 | |||
846 | searchString[0] = '\0'; | ||
847 | |||
848 | if (lastNum) | ||
849 | deleteLines(1, lastNum); | ||
850 | |||
851 | lastNum = 0; | ||
852 | curNum = 0; | ||
853 | curLine = NULL; | ||
854 | } | ||
855 | |||
856 | |||
857 | /* | ||
858 | * Read lines from a file at the specified line number. | ||
859 | * Returns TRUE if the file was successfully read. | ||
860 | */ | ||
861 | static int readLines(const char * file, int num) | ||
862 | { | ||
863 | int fd, cc; | ||
864 | int len, lineCount, charCount; | ||
865 | char *cp; | ||
866 | |||
867 | if ((num < 1) || (num > lastNum + 1)) | ||
868 | { | ||
869 | bb_error_msg("Bad line for read"); | ||
870 | |||
871 | return FALSE; | ||
872 | } | ||
873 | |||
874 | fd = open(file, 0); | ||
875 | |||
876 | if (fd < 0) | ||
877 | { | ||
878 | perror(file); | ||
879 | |||
880 | return FALSE; | ||
881 | } | ||
882 | |||
883 | bufPtr = bufBase; | ||
884 | bufUsed = 0; | ||
885 | lineCount = 0; | ||
886 | charCount = 0; | ||
887 | cc = 0; | ||
888 | |||
889 | printf("\"%s\", ", file); | ||
890 | fflush(stdout); | ||
891 | |||
892 | do | ||
893 | { | ||
894 | cp = memchr(bufPtr, '\n', bufUsed); | ||
895 | |||
896 | if (cp) | ||
897 | { | ||
898 | len = (cp - bufPtr) + 1; | ||
899 | |||
900 | if (!insertLine(num, bufPtr, len)) | ||
901 | { | ||
902 | close(fd); | ||
903 | |||
904 | return FALSE; | ||
905 | } | ||
906 | |||
907 | bufPtr += len; | ||
908 | bufUsed -= len; | ||
909 | charCount += len; | ||
910 | lineCount++; | ||
911 | num++; | ||
912 | |||
913 | continue; | ||
914 | } | ||
915 | |||
916 | if (bufPtr != bufBase) | ||
917 | { | ||
918 | memcpy(bufBase, bufPtr, bufUsed); | ||
919 | bufPtr = bufBase + bufUsed; | ||
920 | } | ||
921 | |||
922 | if (bufUsed >= bufSize) | ||
923 | { | ||
924 | len = (bufSize * 3) / 2; | ||
925 | cp = realloc(bufBase, len); | ||
926 | |||
927 | if (cp == NULL) | ||
928 | { | ||
929 | bb_error_msg("No memory for buffer"); | ||
930 | close(fd); | ||
931 | |||
932 | return FALSE; | ||
933 | } | ||
934 | |||
935 | bufBase = cp; | ||
936 | bufPtr = bufBase + bufUsed; | ||
937 | bufSize = len; | ||
938 | } | ||
939 | |||
940 | cc = read(fd, bufPtr, bufSize - bufUsed); | ||
941 | bufUsed += cc; | ||
942 | bufPtr = bufBase; | ||
943 | |||
944 | } | ||
945 | while (cc > 0); | ||
946 | |||
947 | if (cc < 0) | ||
948 | { | ||
949 | perror(file); | ||
950 | close(fd); | ||
951 | |||
952 | return FALSE; | ||
953 | } | ||
954 | |||
955 | if (bufUsed) | ||
956 | { | ||
957 | if (!insertLine(num, bufPtr, bufUsed)) | ||
958 | { | ||
959 | close(fd); | ||
960 | |||
961 | return -1; | ||
962 | } | ||
963 | |||
964 | lineCount++; | ||
965 | charCount += bufUsed; | ||
966 | } | ||
967 | |||
968 | close(fd); | ||
969 | |||
970 | printf("%d lines%s, %d chars\n", lineCount, | ||
971 | (bufUsed ? " (incomplete)" : ""), charCount); | ||
972 | |||
973 | return TRUE; | ||
974 | } | ||
975 | |||
976 | |||
977 | /* | ||
978 | * Write the specified lines out to the specified file. | ||
979 | * Returns TRUE if successful, or FALSE on an error with a message output. | ||
980 | */ | ||
981 | static int writeLines(const char * file, int num1, int num2) | ||
982 | { | ||
983 | LINE *lp; | ||
984 | int fd, lineCount, charCount; | ||
985 | |||
986 | if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) | ||
987 | { | ||
988 | bb_error_msg("Bad line range for write"); | ||
989 | |||
990 | return FALSE; | ||
991 | } | ||
992 | |||
993 | lineCount = 0; | ||
994 | charCount = 0; | ||
995 | |||
996 | fd = creat(file, 0666); | ||
997 | |||
998 | if (fd < 0) { | ||
999 | perror(file); | ||
1000 | |||
1001 | return FALSE; | ||
1002 | } | ||
1003 | |||
1004 | printf("\"%s\", ", file); | ||
1005 | fflush(stdout); | ||
1006 | |||
1007 | lp = findLine(num1); | ||
1008 | |||
1009 | if (lp == NULL) | ||
1010 | { | ||
1011 | close(fd); | ||
1012 | |||
1013 | return FALSE; | ||
1014 | } | ||
1015 | |||
1016 | while (num1++ <= num2) | ||
1017 | { | ||
1018 | if (write(fd, lp->data, lp->len) != lp->len) | ||
1019 | { | ||
1020 | perror(file); | ||
1021 | close(fd); | ||
1022 | |||
1023 | return FALSE; | ||
1024 | } | ||
1025 | |||
1026 | charCount += lp->len; | ||
1027 | lineCount++; | ||
1028 | lp = lp->next; | ||
1029 | } | ||
1030 | |||
1031 | if (close(fd) < 0) | ||
1032 | { | ||
1033 | perror(file); | ||
1034 | |||
1035 | return FALSE; | ||
1036 | } | ||
1037 | |||
1038 | printf("%d lines, %d chars\n", lineCount, charCount); | ||
1039 | |||
1040 | return TRUE; | ||
1041 | } | ||
1042 | |||
1043 | |||
1044 | /* | ||
1045 | * Print lines in a specified range. | ||
1046 | * The last line printed becomes the current line. | ||
1047 | * If expandFlag is TRUE, then the line is printed specially to | ||
1048 | * show magic characters. | ||
1049 | */ | ||
1050 | static int printLines(int num1, int num2, int expandFlag) | ||
1051 | { | ||
1052 | const LINE *lp; | ||
1053 | const char *cp; | ||
1054 | int ch, count; | ||
1055 | |||
1056 | if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) | ||
1057 | { | ||
1058 | bb_error_msg("Bad line range for print"); | ||
1059 | |||
1060 | return FALSE; | ||
1061 | } | ||
1062 | |||
1063 | lp = findLine(num1); | ||
1064 | |||
1065 | if (lp == NULL) | ||
1066 | return FALSE; | ||
1067 | |||
1068 | while (num1 <= num2) | ||
1069 | { | ||
1070 | if (!expandFlag) | ||
1071 | { | ||
1072 | write(1, lp->data, lp->len); | ||
1073 | setCurNum(num1++); | ||
1074 | lp = lp->next; | ||
1075 | |||
1076 | continue; | ||
1077 | } | ||
1078 | |||
1079 | /* | ||
1080 | * Show control characters and characters with the | ||
1081 | * high bit set specially. | ||
1082 | */ | ||
1083 | cp = lp->data; | ||
1084 | count = lp->len; | ||
1085 | |||
1086 | if ((count > 0) && (cp[count - 1] == '\n')) | ||
1087 | count--; | ||
1088 | |||
1089 | while (count-- > 0) | ||
1090 | { | ||
1091 | ch = *cp++; | ||
1092 | |||
1093 | if (ch & 0x80) | ||
1094 | { | ||
1095 | fputs("M-", stdout); | ||
1096 | ch &= 0x7f; | ||
1097 | } | ||
1098 | |||
1099 | if (ch < ' ') | ||
1100 | { | ||
1101 | fputc('^', stdout); | ||
1102 | ch += '@'; | ||
1103 | } | ||
1104 | |||
1105 | if (ch == 0x7f) | ||
1106 | { | ||
1107 | fputc('^', stdout); | ||
1108 | ch = '?'; | ||
1109 | } | ||
1110 | |||
1111 | fputc(ch, stdout); | ||
1112 | } | ||
1113 | |||
1114 | fputs("$\n", stdout); | ||
1115 | |||
1116 | setCurNum(num1++); | ||
1117 | lp = lp->next; | ||
1118 | } | ||
1119 | |||
1120 | return TRUE; | ||
1121 | } | ||
1122 | |||
1123 | |||
1124 | /* | ||
1125 | * Insert a new line with the specified text. | ||
1126 | * The line is inserted so as to become the specified line, | ||
1127 | * thus pushing any existing and further lines down one. | ||
1128 | * The inserted line is also set to become the current line. | ||
1129 | * Returns TRUE if successful. | ||
1130 | */ | ||
1131 | static int insertLine(int num, const char * data, int len) | ||
1132 | { | ||
1133 | LINE *newLp, *lp; | ||
1134 | |||
1135 | if ((num < 1) || (num > lastNum + 1)) | ||
1136 | { | ||
1137 | bb_error_msg("Inserting at bad line number"); | ||
1138 | |||
1139 | return FALSE; | ||
1140 | } | ||
1141 | |||
1142 | newLp = (LINE *) malloc(sizeof(LINE) + len - 1); | ||
1143 | |||
1144 | if (newLp == NULL) | ||
1145 | { | ||
1146 | bb_error_msg("Failed to allocate memory for line"); | ||
1147 | |||
1148 | return FALSE; | ||
1149 | } | ||
1150 | |||
1151 | memcpy(newLp->data, data, len); | ||
1152 | newLp->len = len; | ||
1153 | |||
1154 | if (num > lastNum) | ||
1155 | lp = &lines; | ||
1156 | else | ||
1157 | { | ||
1158 | lp = findLine(num); | ||
1159 | |||
1160 | if (lp == NULL) | ||
1161 | { | ||
1162 | free((char *) newLp); | ||
1163 | |||
1164 | return FALSE; | ||
1165 | } | ||
1166 | } | ||
1167 | |||
1168 | newLp->next = lp; | ||
1169 | newLp->prev = lp->prev; | ||
1170 | lp->prev->next = newLp; | ||
1171 | lp->prev = newLp; | ||
1172 | |||
1173 | lastNum++; | ||
1174 | dirty = TRUE; | ||
1175 | |||
1176 | return setCurNum(num); | ||
1177 | } | ||
1178 | |||
1179 | |||
1180 | /* | ||
1181 | * Delete lines from the given range. | ||
1182 | */ | ||
1183 | static int deleteLines(int num1, int num2) | ||
1184 | { | ||
1185 | LINE *lp, *nlp, *plp; | ||
1186 | int count; | ||
1187 | |||
1188 | if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) | ||
1189 | { | ||
1190 | bb_error_msg("Bad line numbers for delete"); | ||
1191 | |||
1192 | return FALSE; | ||
1193 | } | ||
1194 | |||
1195 | lp = findLine(num1); | ||
1196 | |||
1197 | if (lp == NULL) | ||
1198 | return FALSE; | ||
1199 | |||
1200 | if ((curNum >= num1) && (curNum <= num2)) | ||
1201 | { | ||
1202 | if (num2 < lastNum) | ||
1203 | setCurNum(num2 + 1); | ||
1204 | else if (num1 > 1) | ||
1205 | setCurNum(num1 - 1); | ||
1206 | else | ||
1207 | curNum = 0; | ||
1208 | } | ||
1209 | |||
1210 | count = num2 - num1 + 1; | ||
1211 | |||
1212 | if (curNum > num2) | ||
1213 | curNum -= count; | ||
1214 | |||
1215 | lastNum -= count; | ||
1216 | |||
1217 | while (count-- > 0) | ||
1218 | { | ||
1219 | nlp = lp->next; | ||
1220 | plp = lp->prev; | ||
1221 | plp->next = nlp; | ||
1222 | nlp->prev = plp; | ||
1223 | lp->next = NULL; | ||
1224 | lp->prev = NULL; | ||
1225 | lp->len = 0; | ||
1226 | free(lp); | ||
1227 | lp = nlp; | ||
1228 | } | ||
1229 | |||
1230 | dirty = TRUE; | ||
1231 | |||
1232 | return TRUE; | ||
1233 | } | ||
1234 | |||
1235 | |||
1236 | /* | ||
1237 | * Search for a line which contains the specified string. | ||
1238 | * If the string is NULL, then the previously searched for string | ||
1239 | * is used. The currently searched for string is saved for future use. | ||
1240 | * Returns the line number which matches, or 0 if there was no match | ||
1241 | * with an error printed. | ||
1242 | */ | ||
1243 | static int searchLines(const char *str, int num1, int num2) | ||
1244 | { | ||
1245 | const LINE *lp; | ||
1246 | int len; | ||
1247 | |||
1248 | if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) | ||
1249 | { | ||
1250 | bb_error_msg("Bad line numbers for search"); | ||
1251 | |||
1252 | return 0; | ||
1253 | } | ||
1254 | |||
1255 | if (*str == '\0') | ||
1256 | { | ||
1257 | if (searchString[0] == '\0') | ||
1258 | { | ||
1259 | bb_error_msg("No previous search string"); | ||
1260 | |||
1261 | return 0; | ||
1262 | } | ||
1263 | |||
1264 | str = searchString; | ||
1265 | } | ||
1266 | |||
1267 | if (str != searchString) | ||
1268 | strcpy(searchString, str); | ||
1269 | |||
1270 | len = strlen(str); | ||
1271 | |||
1272 | lp = findLine(num1); | ||
1273 | |||
1274 | if (lp == NULL) | ||
1275 | return 0; | ||
1276 | |||
1277 | while (num1 <= num2) | ||
1278 | { | ||
1279 | if (findString(lp, str, len, 0) >= 0) | ||
1280 | return num1; | ||
1281 | |||
1282 | num1++; | ||
1283 | lp = lp->next; | ||
1284 | } | ||
1285 | |||
1286 | bb_error_msg("Cannot find string \"%s\"", str); | ||
1287 | |||
1288 | return 0; | ||
1289 | } | ||
1290 | |||
1291 | |||
1292 | /* | ||
1293 | * Return a pointer to the specified line number. | ||
1294 | */ | ||
1295 | static LINE *findLine(int num) | ||
1296 | { | ||
1297 | LINE *lp; | ||
1298 | int lnum; | ||
1299 | |||
1300 | if ((num < 1) || (num > lastNum)) | ||
1301 | { | ||
1302 | bb_error_msg("Line number %d does not exist", num); | ||
1303 | |||
1304 | return NULL; | ||
1305 | } | ||
1306 | |||
1307 | if (curNum <= 0) | ||
1308 | { | ||
1309 | curNum = 1; | ||
1310 | curLine = lines.next; | ||
1311 | } | ||
1312 | |||
1313 | if (num == curNum) | ||
1314 | return curLine; | ||
1315 | |||
1316 | lp = curLine; | ||
1317 | lnum = curNum; | ||
1318 | |||
1319 | if (num < (curNum / 2)) | ||
1320 | { | ||
1321 | lp = lines.next; | ||
1322 | lnum = 1; | ||
1323 | } | ||
1324 | else if (num > ((curNum + lastNum) / 2)) | ||
1325 | { | ||
1326 | lp = lines.prev; | ||
1327 | lnum = lastNum; | ||
1328 | } | ||
1329 | |||
1330 | while (lnum < num) | ||
1331 | { | ||
1332 | lp = lp->next; | ||
1333 | lnum++; | ||
1334 | } | ||
1335 | |||
1336 | while (lnum > num) | ||
1337 | { | ||
1338 | lp = lp->prev; | ||
1339 | lnum--; | ||
1340 | } | ||
1341 | |||
1342 | return lp; | ||
1343 | } | ||
1344 | |||
1345 | |||
1346 | /* | ||
1347 | * Set the current line number. | ||
1348 | * Returns TRUE if successful. | ||
1349 | */ | ||
1350 | static int setCurNum(int num) | ||
1351 | { | ||
1352 | LINE *lp; | ||
1353 | |||
1354 | lp = findLine(num); | ||
1355 | |||
1356 | if (lp == NULL) | ||
1357 | return FALSE; | ||
1358 | |||
1359 | curNum = num; | ||
1360 | curLine = lp; | ||
1361 | |||
1362 | return TRUE; | ||
1363 | } | ||