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