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