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