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