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