process utilities related style cleanup
[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 #include <stdio.h>
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <string.h>
15 #include <time.h>
16 #include <ctype.h>
17 #include <sys/param.h>
18 #include "busybox.h"
19
20 #define USERSIZE        1024    /* max line length typed in by user */
21 #define INITBUF_SIZE    1024    /* initial buffer size */
22 typedef struct LINE {
23         struct LINE *next;
24         struct LINE *prev;
25         int len;
26         char data[1];
27 } LINE;
28
29 static LINE lines, *curLine;
30 static int curNum, lastNum, marks[26], dirty;
31 static char *bufBase, *bufPtr, *fileName, searchString[USERSIZE];
32 static int bufUsed, bufSize;
33
34 static void doCommands(void);
35 static void subCommand(const char *cmd, int num1, int num2);
36 static int getNum(const char **retcp, int *retHaveNum, int *retNum);
37 static int setCurNum(int num);
38 static int initEdit(void);
39 static void termEdit(void);
40 static void addLines(int num);
41 static int insertLine(int num, const char *data, int len);
42 static int deleteLines(int num1, int num2);
43 static int printLines(int num1, int num2, int expandFlag);
44 static int writeLines(const char *file, int num1, int num2);
45 static int readLines(const char *file, int num);
46 static int searchLines(const char *str, int num1, int num2);
47 static LINE *findLine(int num);
48
49 static int findString(const LINE *lp, const char * str, int len, int offset);
50
51 int ed_main(int argc, char **argv)
52 {
53         if (!initEdit())
54                 return EXIT_FAILURE;
55
56         if (argc > 1) {
57                 fileName = strdup(argv[1]);
58
59                 if (fileName == NULL) {
60                         bb_error_msg("No memory");
61                         termEdit();
62                         return EXIT_SUCCESS;
63                 }
64
65                 if (!readLines(fileName, 1)) {
66                         termEdit();
67                         return EXIT_SUCCESS;
68                 }
69
70                 if (lastNum)
71                         setCurNum(1);
72
73                 dirty = FALSE;
74         }
75
76         doCommands();
77
78         termEdit();
79         return EXIT_SUCCESS;
80 }
81
82 /*
83  * Read commands until we are told to stop.
84  */
85 static void doCommands(void)
86 {
87         const char *cp;
88         char *endbuf, *newname, buf[USERSIZE];
89         int len, num1, num2, have1, have2;
90
91         while (TRUE)
92         {
93                 printf(": ");
94                 fflush(stdout);
95
96                 if (fgets(buf, sizeof(buf), stdin) == NULL)
97                         return;
98
99                 len = strlen(buf);
100
101                 if (len == 0)
102                         return;
103
104                 endbuf = &buf[len - 1];
105
106                 if (*endbuf != '\n')
107                 {
108                         bb_error_msg("Command line too long");
109
110                         do
111                         {
112                                 len = fgetc(stdin);
113                         }
114                         while ((len != EOF) && (len != '\n'));
115
116                         continue;
117                 }
118
119                 while ((endbuf > buf) && isblank(endbuf[-1]))
120                         endbuf--;
121
122                 *endbuf = '\0';
123
124                 cp = buf;
125
126                 while (isblank(*cp))
127                         cp++;
128
129                 have1 = FALSE;
130                 have2 = FALSE;
131
132                 if ((curNum == 0) && (lastNum > 0))
133                 {
134                         curNum = 1;
135                         curLine = lines.next;
136                 }
137
138                 if (!getNum(&cp, &have1, &num1))
139                         continue;
140
141                 while (isblank(*cp))
142                         cp++;
143
144                 if (*cp == ',')
145                 {
146                         cp++;
147
148                         if (!getNum(&cp, &have2, &num2))
149                                 continue;
150
151                         if (!have1)
152                                 num1 = 1;
153
154                         if (!have2)
155                                 num2 = lastNum;
156
157                         have1 = TRUE;
158                         have2 = TRUE;
159                 }
160
161                 if (!have1)
162                         num1 = curNum;
163
164                 if (!have2)
165                         num2 = num1;
166
167                 switch (*cp++)
168                 {
169                         case 'a':
170                                 addLines(num1 + 1);
171                                 break;
172
173                         case 'c':
174                                 deleteLines(num1, num2);
175                                 addLines(num1);
176                                 break;
177
178                         case 'd':
179                                 deleteLines(num1, num2);
180                                 break;
181
182                         case 'f':
183                                 if (*cp && !isblank(*cp))
184                                 {
185                                         bb_error_msg("Bad file command");
186                                         break;
187                                 }
188
189                                 while (isblank(*cp))
190                                         cp++;
191
192                                 if (*cp == '\0')
193                                 {
194                                         if (fileName)
195                                                 printf("\"%s\"\n", fileName);
196                                         else
197                                                 printf("No file name\n");
198
199                                         break;
200                                 }
201
202                                 newname = strdup(cp);
203
204                                 if (newname == NULL)
205                                 {
206                                         bb_error_msg("No memory for file name");
207                                         break;
208                                 }
209
210                                 if (fileName)
211                                         free(fileName);
212
213                                 fileName = newname;
214                                 break;
215
216                         case 'i':
217                                 addLines(num1);
218                                 break;
219
220                         case 'k':
221                                 while (isblank(*cp))
222                                         cp++;
223
224                                 if ((*cp < 'a') || (*cp > 'a') || cp[1])
225                                 {
226                                         bb_error_msg("Bad mark name");
227                                         break;
228                                 }
229
230                                 marks[*cp - 'a'] = num2;
231                                 break;
232
233                         case 'l':
234                                 printLines(num1, num2, TRUE);
235                                 break;
236
237                         case 'p':
238                                 printLines(num1, num2, FALSE);
239                                 break;
240
241                         case 'q':
242                                 while (isblank(*cp))
243                                         cp++;
244
245                                 if (have1 || *cp)
246                                 {
247                                         bb_error_msg("Bad quit command");
248                                         break;
249                                 }
250
251                                 if (!dirty)
252                                         return;
253
254                                 printf("Really quit? ");
255                                 fflush(stdout);
256
257                                 buf[0] = '\0';
258                                 fgets(buf, sizeof(buf), stdin);
259                                 cp = buf;
260
261                                 while (isblank(*cp))
262                                         cp++;
263
264                                 if ((*cp == 'y') || (*cp == 'Y'))
265                                         return;
266
267                                 break;
268
269                         case 'r':
270                                 if (*cp && !isblank(*cp))
271                                 {
272                                         bb_error_msg("Bad read command");
273                                         break;
274                                 }
275
276                                 while (isblank(*cp))
277                                         cp++;
278
279                                 if (*cp == '\0')
280                                 {
281                                         bb_error_msg("No file name");
282                                         break;
283                                 }
284
285                                 if (!have1)
286                                         num1 = lastNum;
287
288                                 if (readLines(cp, num1 + 1))
289                                         break;
290
291                                 if (fileName == NULL)
292                                         fileName = strdup(cp);
293
294                                 break;
295
296                         case 's':
297                                 subCommand(cp, num1, num2);
298                                 break;
299
300                         case 'w':
301                                 if (*cp && !isblank(*cp))
302                                 {
303                                         bb_error_msg("Bad write command");
304                                         break;
305                                 }
306
307                                 while (isblank(*cp))
308                                         cp++;
309
310                                 if (!have1) {
311                                         num1 = 1;
312                                         num2 = lastNum;
313                                 }
314
315                                 if (*cp == '\0')
316                                         cp = fileName;
317
318                                 if (cp == NULL)
319                                 {
320                                         bb_error_msg("No file name specified");
321                                         break;
322                                 }
323
324                                 writeLines(cp, num1, num2);
325                                 break;
326
327                         case 'z':
328                                 switch (*cp)
329                                 {
330                                 case '-':
331                                         printLines(curNum-21, curNum, FALSE);
332                                         break;
333                                 case '.':
334                                         printLines(curNum-11, curNum+10, FALSE);
335                                         break;
336                                 default:
337                                         printLines(curNum, curNum+21, FALSE);
338                                         break;
339                                 }
340                                 break;
341
342                         case '.':
343                                 if (have1)
344                                 {
345                                         bb_error_msg("No arguments allowed");
346                                         break;
347                                 }
348
349                                 printLines(curNum, curNum, FALSE);
350                                 break;
351
352                         case '-':
353                                 if (setCurNum(curNum - 1))
354                                         printLines(curNum, curNum, FALSE);
355
356                                 break;
357
358                         case '=':
359                                 printf("%d\n", num1);
360                                 break;
361
362                         case '\0':
363                                 if (have1)
364                                 {
365                                         printLines(num2, num2, FALSE);
366                                         break;
367                                 }
368
369                                 if (setCurNum(curNum + 1))
370                                         printLines(curNum, curNum, FALSE);
371
372                                 break;
373
374                         default:
375                                 bb_error_msg("Unimplemented command");
376                                 break;
377                 }
378         }
379 }
380
381
382 /*
383  * Do the substitute command.
384  * The current line is set to the last substitution done.
385  */
386 static void subCommand(const char * cmd, int num1, int num2)
387 {
388         char *cp, *oldStr, *newStr, buf[USERSIZE];
389         int     delim, oldLen, newLen, deltaLen, offset;
390         LINE *lp, *nlp;
391         int globalFlag, printFlag, didSub, needPrint;
392
393         if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
394         {
395                 bb_error_msg("Bad line range for substitute");
396
397                 return;
398         }
399
400         globalFlag = FALSE;
401         printFlag = FALSE;
402         didSub = FALSE;
403         needPrint = FALSE;
404
405         /*
406          * Copy the command so we can modify it.
407          */
408         strcpy(buf, cmd);
409         cp = buf;
410
411         if (isblank(*cp) || (*cp == '\0'))
412         {
413                 bb_error_msg("Bad delimiter for substitute");
414
415                 return;
416         }
417
418         delim = *cp++;
419         oldStr = cp;
420
421         cp = strchr(cp, delim);
422
423         if (cp == NULL)
424         {
425                 bb_error_msg("Missing 2nd delimiter for substitute");
426
427                 return;
428         }
429
430         *cp++ = '\0';
431
432         newStr = cp;
433         cp = strchr(cp, delim);
434
435         if (cp)
436                 *cp++ = '\0';
437         else
438                 cp = "";
439
440         while (*cp) switch (*cp++)
441         {
442                 case 'g':
443                         globalFlag = TRUE;
444                         break;
445
446                 case 'p':
447                         printFlag = TRUE;
448                         break;
449
450                 default:
451                         bb_error_msg("Unknown option for substitute");
452
453                         return;
454         }
455
456         if (*oldStr == '\0')
457         {
458                 if (searchString[0] == '\0')
459                 {
460                         bb_error_msg("No previous search string");
461
462                         return;
463                 }
464
465                 oldStr = searchString;
466         }
467
468         if (oldStr != searchString)
469                 strcpy(searchString, oldStr);
470
471         lp = findLine(num1);
472
473         if (lp == NULL)
474                 return;
475
476         oldLen = strlen(oldStr);
477         newLen = strlen(newStr);
478         deltaLen = newLen - oldLen;
479         offset = 0;
480         nlp = NULL;
481
482         while (num1 <= num2)
483         {
484                 offset = findString(lp, oldStr, oldLen, offset);
485
486                 if (offset < 0)
487                 {
488                         if (needPrint)
489                         {
490                                 printLines(num1, num1, FALSE);
491                                 needPrint = FALSE;
492                         }
493
494                         offset = 0;
495                         lp = lp->next;
496                         num1++;
497
498                         continue;
499                 }
500
501                 needPrint = printFlag;
502                 didSub = TRUE;
503                 dirty = TRUE;
504
505                 /*
506                  * If the replacement string is the same size or shorter
507                  * than the old string, then the substitution is easy.
508                  */
509                 if (deltaLen <= 0)
510                 {
511                         memcpy(&lp->data[offset], newStr, newLen);
512
513                         if (deltaLen)
514                         {
515                                 memcpy(&lp->data[offset + newLen],
516                                         &lp->data[offset + oldLen],
517                                         lp->len - offset - oldLen);
518
519                                 lp->len += deltaLen;
520                         }
521
522                         offset += newLen;
523
524                         if (globalFlag)
525                                 continue;
526
527                         if (needPrint)
528                         {
529                                 printLines(num1, num1, FALSE);
530                                 needPrint = FALSE;
531                         }
532
533                         lp = lp->next;
534                         num1++;
535
536                         continue;
537                 }
538
539                 /*
540                  * The new string is larger, so allocate a new line
541                  * structure and use that.  Link it in in place of
542                  * the old line structure.
543                  */
544                 nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen);
545
546                 if (nlp == NULL)
547                 {
548                         bb_error_msg("Cannot get memory for line");
549
550                         return;
551                 }
552
553                 nlp->len = lp->len + deltaLen;
554
555                 memcpy(nlp->data, lp->data, offset);
556
557                 memcpy(&nlp->data[offset], newStr, newLen);
558
559                 memcpy(&nlp->data[offset + newLen],
560                         &lp->data[offset + oldLen],
561                         lp->len - offset - oldLen);
562
563                 nlp->next = lp->next;
564                 nlp->prev = lp->prev;
565                 nlp->prev->next = nlp;
566                 nlp->next->prev = nlp;
567
568                 if (curLine == lp)
569                         curLine = nlp;
570
571                 free(lp);
572                 lp = nlp;
573
574                 offset += newLen;
575
576                 if (globalFlag)
577                         continue;
578
579                 if (needPrint)
580                 {
581                         printLines(num1, num1, FALSE);
582                         needPrint = FALSE;
583                 }
584
585                 lp = lp->next;
586                 num1++;
587         }
588
589         if (!didSub)
590                 bb_error_msg("No substitutions found for \"%s\"", oldStr);
591 }
592
593
594 /*
595  * Search a line for the specified string starting at the specified
596  * offset in the line.  Returns the offset of the found string, or -1.
597  */
598 static int findString( const LINE * lp, const char * str, int len, int offset)
599 {
600         int left;
601         const char *cp, *ncp;
602
603         cp = &lp->data[offset];
604         left = lp->len - offset;
605
606         while (left >= len)
607         {
608                 ncp = memchr(cp, *str, left);
609
610                 if (ncp == NULL)
611                         return -1;
612
613                 left -= (ncp - cp);
614
615                 if (left < len)
616                         return -1;
617
618                 cp = ncp;
619
620                 if (memcmp(cp, str, len) == 0)
621                         return (cp - lp->data);
622
623                 cp++;
624                 left--;
625         }
626
627         return -1;
628 }
629
630
631 /*
632  * Add lines which are typed in by the user.
633  * The lines are inserted just before the specified line number.
634  * The lines are terminated by a line containing a single dot (ugly!),
635  * or by an end of file.
636  */
637 static void addLines(int num)
638 {
639         int     len;
640         char    buf[USERSIZE + 1];
641
642         while (fgets(buf, sizeof(buf), stdin))
643         {
644                 if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
645                         return;
646
647                 len = strlen(buf);
648
649                 if (len == 0)
650                         return;
651
652                 if (buf[len - 1] != '\n')
653                 {
654                         bb_error_msg("Line too long");
655
656                         do
657                         {
658                                 len = fgetc(stdin);
659                         }
660                         while ((len != EOF) && (len != '\n'));
661
662                         return;
663                 }
664
665                 if (!insertLine(num++, buf, len))
666                         return;
667         }
668 }
669
670
671 /*
672  * Parse a line number argument if it is present.  This is a sum
673  * or difference of numbers, '.', '$', 'x, or a search string.
674  * Returns TRUE if successful (whether or not there was a number).
675  * Returns FALSE if there was a parsing error, with a message output.
676  * Whether there was a number is returned indirectly, as is the number.
677  * The character pointer which stopped the scan is also returned.
678  */
679 static int getNum(const char **retcp, int *retHaveNum, int *retNum)
680 {
681         const char *cp;
682         char *endStr, str[USERSIZE];
683         int haveNum, value, num, sign;
684
685         cp = *retcp;
686         haveNum = FALSE;
687         value = 0;
688         sign = 1;
689
690         while (TRUE)
691         {
692                 while (isblank(*cp))
693                         cp++;
694
695                 switch (*cp)
696                 {
697                         case '.':
698                                 haveNum = TRUE;
699                                 num = curNum;
700                                 cp++;
701                                 break;
702
703                         case '$':
704                                 haveNum = TRUE;
705                                 num = lastNum;
706                                 cp++;
707                                 break;
708
709                         case '\'':
710                                 cp++;
711
712                                 if ((*cp < 'a') || (*cp > 'z'))
713                                 {
714                                         bb_error_msg("Bad mark name");
715
716                                         return FALSE;
717                                 }
718
719                                 haveNum = TRUE;
720                                 num = marks[*cp++ - 'a'];
721                                 break;
722
723                         case '/':
724                                 strcpy(str, ++cp);
725                                 endStr = strchr(str, '/');
726
727                                 if (endStr)
728                                 {
729                                         *endStr++ = '\0';
730                                         cp += (endStr - str);
731                                 }
732                                 else
733                                         cp = "";
734
735                                 num = searchLines(str, curNum, lastNum);
736
737                                 if (num == 0)
738                                         return FALSE;
739
740                                 haveNum = TRUE;
741                                 break;
742
743                         default:
744                                 if (!isdigit(*cp))
745                                 {
746                                         *retcp = cp;
747                                         *retHaveNum = haveNum;
748                                         *retNum = value;
749
750                                         return TRUE;
751                                 }
752
753                                 num = 0;
754
755                                 while (isdigit(*cp))
756                                         num = num * 10 + *cp++ - '0';
757
758                                 haveNum = TRUE;
759                                 break;
760                 }
761
762                 value += num * sign;
763
764                 while (isblank(*cp))
765                         cp++;
766
767                 switch (*cp)
768                 {
769                         case '-':
770                                 sign = -1;
771                                 cp++;
772                                 break;
773
774                         case '+':
775                                 sign = 1;
776                                 cp++;
777                                 break;
778
779                         default:
780                                 *retcp = cp;
781                                 *retHaveNum = haveNum;
782                                 *retNum = value;
783
784                                 return TRUE;
785                 }
786         }
787 }
788
789
790 /*
791  * Initialize everything for editing.
792  */
793 static int initEdit(void)
794 {
795         int     i;
796
797         bufSize = INITBUF_SIZE;
798         bufBase = malloc(bufSize);
799
800         if (bufBase == NULL)
801         {
802                 bb_error_msg("No memory for buffer");
803
804                 return FALSE;
805         }
806
807         bufPtr = bufBase;
808         bufUsed = 0;
809
810         lines.next = &lines;
811         lines.prev = &lines;
812
813         curLine = NULL;
814         curNum = 0;
815         lastNum = 0;
816         dirty = FALSE;
817         fileName = NULL;
818         searchString[0] = '\0';
819
820         for (i = 0; i < 26; i++)
821                 marks[i] = 0;
822
823         return TRUE;
824 }
825
826
827 /*
828  * Finish editing.
829  */
830 static void termEdit(void)
831 {
832         if (bufBase)
833                 free(bufBase);
834
835         bufBase = NULL;
836         bufPtr = NULL;
837         bufSize = 0;
838         bufUsed = 0;
839
840         if (fileName)
841                 free(fileName);
842
843         fileName = NULL;
844
845         searchString[0] = '\0';
846
847         if (lastNum)
848                 deleteLines(1, lastNum);
849
850         lastNum = 0;
851         curNum = 0;
852         curLine = NULL;
853 }
854
855
856 /*
857  * Read lines from a file at the specified line number.
858  * Returns TRUE if the file was successfully read.
859  */
860 static int readLines(const char * file, int num)
861 {
862         int     fd, cc;
863         int len, lineCount, charCount;
864         char *cp;
865
866         if ((num < 1) || (num > lastNum + 1))
867         {
868                 bb_error_msg("Bad line for read");
869
870                 return FALSE;
871         }
872
873         fd = open(file, 0);
874
875         if (fd < 0)
876         {
877                 perror(file);
878
879                 return FALSE;
880         }
881
882         bufPtr = bufBase;
883         bufUsed = 0;
884         lineCount = 0;
885         charCount = 0;
886         cc = 0;
887
888         printf("\"%s\", ", file);
889         fflush(stdout);
890
891         do
892         {
893                 cp = memchr(bufPtr, '\n', bufUsed);
894
895                 if (cp)
896                 {
897                         len = (cp - bufPtr) + 1;
898
899                         if (!insertLine(num, bufPtr, len))
900                         {
901                                 close(fd);
902
903                                 return FALSE;
904                         }
905
906                         bufPtr += len;
907                         bufUsed -= len;
908                         charCount += len;
909                         lineCount++;
910                         num++;
911
912                         continue;
913                 }
914
915                 if (bufPtr != bufBase)
916                 {
917                         memcpy(bufBase, bufPtr, bufUsed);
918                         bufPtr = bufBase + bufUsed;
919                 }
920
921                 if (bufUsed >= bufSize)
922                 {
923                         len = (bufSize * 3) / 2;
924                         cp = realloc(bufBase, len);
925
926                         if (cp == NULL)
927                         {
928                                 bb_error_msg("No memory for buffer");
929                                 close(fd);
930
931                                 return FALSE;
932                         }
933
934                         bufBase = cp;
935                         bufPtr = bufBase + bufUsed;
936                         bufSize = len;
937                 }
938
939                 cc = read(fd, bufPtr, bufSize - bufUsed);
940                 bufUsed += cc;
941                 bufPtr = bufBase;
942
943         }
944         while (cc > 0);
945
946         if (cc < 0)
947         {
948                 perror(file);
949                 close(fd);
950
951                 return FALSE;
952         }
953
954         if (bufUsed)
955         {
956                 if (!insertLine(num, bufPtr, bufUsed))
957                 {
958                         close(fd);
959
960                         return -1;
961                 }
962
963                 lineCount++;
964                 charCount += bufUsed;
965         }
966
967         close(fd);
968
969         printf("%d lines%s, %d chars\n", lineCount,
970                 (bufUsed ? " (incomplete)" : ""), charCount);
971
972         return TRUE;
973 }
974
975
976 /*
977  * Write the specified lines out to the specified file.
978  * Returns TRUE if successful, or FALSE on an error with a message output.
979  */
980 static int writeLines(const char * file, int num1, int num2)
981 {
982         LINE *lp;
983         int     fd, lineCount, charCount;
984
985         if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
986         {
987                 bb_error_msg("Bad line range for write");
988
989                 return FALSE;
990         }
991
992         lineCount = 0;
993         charCount = 0;
994
995         fd = creat(file, 0666);
996
997         if (fd < 0) {
998                 perror(file);
999
1000                 return FALSE;
1001         }
1002
1003         printf("\"%s\", ", file);
1004         fflush(stdout);
1005
1006         lp = findLine(num1);
1007
1008         if (lp == NULL)
1009         {
1010                 close(fd);
1011
1012                 return FALSE;
1013         }
1014
1015         while (num1++ <= num2)
1016         {
1017                 if (write(fd, lp->data, lp->len) != lp->len)
1018                 {
1019                         perror(file);
1020                         close(fd);
1021
1022                         return FALSE;
1023                 }
1024
1025                 charCount += lp->len;
1026                 lineCount++;
1027                 lp = lp->next;
1028         }
1029
1030         if (close(fd) < 0)
1031         {
1032                 perror(file);
1033
1034                 return FALSE;
1035         }
1036
1037         printf("%d lines, %d chars\n", lineCount, charCount);
1038
1039         return TRUE;
1040 }
1041
1042
1043 /*
1044  * Print lines in a specified range.
1045  * The last line printed becomes the current line.
1046  * If expandFlag is TRUE, then the line is printed specially to
1047  * show magic characters.
1048  */
1049 static int printLines(int num1, int num2, int expandFlag)
1050 {
1051         const LINE *lp;
1052         const char *cp;
1053         int ch, count;
1054
1055         if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
1056         {
1057                 bb_error_msg("Bad line range for print");
1058
1059                 return FALSE;
1060         }
1061
1062         lp = findLine(num1);
1063
1064         if (lp == NULL)
1065                 return FALSE;
1066
1067         while (num1 <= num2)
1068         {
1069                 if (!expandFlag)
1070                 {
1071                         write(1, lp->data, lp->len);
1072                         setCurNum(num1++);
1073                         lp = lp->next;
1074
1075                         continue;
1076                 }
1077
1078                 /*
1079                  * Show control characters and characters with the
1080                  * high bit set specially.
1081                  */
1082                 cp = lp->data;
1083                 count = lp->len;
1084
1085                 if ((count > 0) && (cp[count - 1] == '\n'))
1086                         count--;
1087
1088                 while (count-- > 0)
1089                 {
1090                         ch = *cp++;
1091
1092                         if (ch & 0x80)
1093                         {
1094                                 fputs("M-", stdout);
1095                                 ch &= 0x7f;
1096                         }
1097
1098                         if (ch < ' ')
1099                         {
1100                                 fputc('^', stdout);
1101                                 ch += '@';
1102                         }
1103
1104                         if (ch == 0x7f)
1105                         {
1106                                 fputc('^', stdout);
1107                                 ch = '?';
1108                         }
1109
1110                         fputc(ch, stdout);
1111                 }
1112
1113                 fputs("$\n", stdout);
1114
1115                 setCurNum(num1++);
1116                 lp = lp->next;
1117         }
1118
1119         return TRUE;
1120 }
1121
1122
1123 /*
1124  * Insert a new line with the specified text.
1125  * The line is inserted so as to become the specified line,
1126  * thus pushing any existing and further lines down one.
1127  * The inserted line is also set to become the current line.
1128  * Returns TRUE if successful.
1129  */
1130 static int insertLine(int num, const char * data, int len)
1131 {
1132         LINE *newLp, *lp;
1133
1134         if ((num < 1) || (num > lastNum + 1))
1135         {
1136                 bb_error_msg("Inserting at bad line number");
1137
1138                 return FALSE;
1139         }
1140
1141         newLp = (LINE *) malloc(sizeof(LINE) + len - 1);
1142
1143         if (newLp == NULL)
1144         {
1145                 bb_error_msg("Failed to allocate memory for line");
1146
1147                 return FALSE;
1148         }
1149
1150         memcpy(newLp->data, data, len);
1151         newLp->len = len;
1152
1153         if (num > lastNum)
1154                 lp = &lines;
1155         else
1156         {
1157                 lp = findLine(num);
1158
1159                 if (lp == NULL)
1160                 {
1161                         free((char *) newLp);
1162
1163                         return FALSE;
1164                 }
1165         }
1166
1167         newLp->next = lp;
1168         newLp->prev = lp->prev;
1169         lp->prev->next = newLp;
1170         lp->prev = newLp;
1171
1172         lastNum++;
1173         dirty = TRUE;
1174
1175         return setCurNum(num);
1176 }
1177
1178
1179 /*
1180  * Delete lines from the given range.
1181  */
1182 static int deleteLines(int num1, int num2)
1183 {
1184         LINE *lp, *nlp, *plp;
1185         int count;
1186
1187         if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
1188         {
1189                 bb_error_msg("Bad line numbers for delete");
1190
1191                 return FALSE;
1192         }
1193
1194         lp = findLine(num1);
1195
1196         if (lp == NULL)
1197                 return FALSE;
1198
1199         if ((curNum >= num1) && (curNum <= num2))
1200         {
1201                 if (num2 < lastNum)
1202                         setCurNum(num2 + 1);
1203                 else if (num1 > 1)
1204                         setCurNum(num1 - 1);
1205                 else
1206                         curNum = 0;
1207         }
1208
1209         count = num2 - num1 + 1;
1210
1211         if (curNum > num2)
1212                 curNum -= count;
1213
1214         lastNum -= count;
1215
1216         while (count-- > 0)
1217         {
1218                 nlp = lp->next;
1219                 plp = lp->prev;
1220                 plp->next = nlp;
1221                 nlp->prev = plp;
1222                 lp->next = NULL;
1223                 lp->prev = NULL;
1224                 lp->len = 0;
1225                 free(lp);
1226                 lp = nlp;
1227         }
1228
1229         dirty = TRUE;
1230
1231         return TRUE;
1232 }
1233
1234
1235 /*
1236  * Search for a line which contains the specified string.
1237  * If the string is NULL, then the previously searched for string
1238  * is used.  The currently searched for string is saved for future use.
1239  * Returns the line number which matches, or 0 if there was no match
1240  * with an error printed.
1241  */
1242 static int searchLines(const char *str, int num1, int num2)
1243 {
1244         const LINE *lp;
1245         int len;
1246
1247         if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
1248         {
1249                 bb_error_msg("Bad line numbers for search");
1250
1251                 return 0;
1252         }
1253
1254         if (*str == '\0')
1255         {
1256                 if (searchString[0] == '\0')
1257                 {
1258                         bb_error_msg("No previous search string");
1259
1260                         return 0;
1261                 }
1262
1263                 str = searchString;
1264         }
1265
1266         if (str != searchString)
1267                 strcpy(searchString, str);
1268
1269         len = strlen(str);
1270
1271         lp = findLine(num1);
1272
1273         if (lp == NULL)
1274                 return 0;
1275
1276         while (num1 <= num2)
1277         {
1278                 if (findString(lp, str, len, 0) >= 0)
1279                         return num1;
1280
1281                 num1++;
1282                 lp = lp->next;
1283         }
1284
1285         bb_error_msg("Cannot find string \"%s\"", str);
1286
1287         return 0;
1288 }
1289
1290
1291 /*
1292  * Return a pointer to the specified line number.
1293  */
1294 static LINE *findLine(int num)
1295 {
1296         LINE *lp;
1297         int lnum;
1298
1299         if ((num < 1) || (num > lastNum))
1300         {
1301                 bb_error_msg("Line number %d does not exist", num);
1302
1303                 return NULL;
1304         }
1305
1306         if (curNum <= 0)
1307         {
1308                 curNum = 1;
1309                 curLine = lines.next;
1310         }
1311
1312         if (num == curNum)
1313                 return curLine;
1314
1315         lp = curLine;
1316         lnum = curNum;
1317
1318         if (num < (curNum / 2))
1319         {
1320                 lp = lines.next;
1321                 lnum = 1;
1322         }
1323         else if (num > ((curNum + lastNum) / 2))
1324         {
1325                 lp = lines.prev;
1326                 lnum = lastNum;
1327         }
1328
1329         while (lnum < num)
1330         {
1331                 lp = lp->next;
1332                 lnum++;
1333         }
1334
1335         while (lnum > num)
1336         {
1337                 lp = lp->prev;
1338                 lnum--;
1339         }
1340
1341         return lp;
1342 }
1343
1344
1345 /*
1346  * Set the current line number.
1347  * Returns TRUE if successful.
1348  */
1349 static int setCurNum(int num)
1350 {
1351         LINE *lp;
1352
1353         lp = findLine(num);
1354
1355         if (lp == NULL)
1356                 return FALSE;
1357
1358         curNum = num;
1359         curLine = lp;
1360
1361         return TRUE;
1362 }