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