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