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