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