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