1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 /*
7  * Copyright 1992,1993 Simmule Turner and Rich Salz.  All rights reserved.
8  *
9  * This software is not subject to any license of the American Telephone
10  * and Telegraph Company or of the Regents of the University of California.
11  *
12  * Permission is granted to anyone to use this software for any purpose on
13  * any computer system, and to alter it and redistribute it freely, subject
14  * to the following restrictions:
15  * 1. The authors are not responsible for the consequences of use of this
16  *    software, no matter how awful, even if they arise from flaws in it.
17  * 2. The origin of this software must not be misrepresented, either by
18  *    explicit claim or by omission.  Since few users ever read sources,
19  *    credits must appear in the documentation.
20  * 3. Altered versions must be plainly marked as such, and must not be
21  *    misrepresented as being the original software.  Since few users
22  *    ever read sources, credits must appear in the documentation.
23  * 4. This notice may not be removed or altered.
24  */
25 
26 
27 /*
28 **  Main editing routines for editline library.
29 */
30 #include "editline.h"
31 #include <signal.h>
32 #include <ctype.h>
33 #include <unistd.h>
34 
35 /*
36 **  Manifest constants.
37 */
38 #define SCREEN_WIDTH	80
39 #define SCREEN_ROWS	24
40 #define NO_ARG		(-1)
41 #define DEL		127
42 #define CTL(x)		((x) & 0x1F)
43 #define ISCTL(x)	((x) && (x) < ' ')
44 #define UNCTL(x)	((x) + 64)
45 #define META(x)		((x) | 0x80)
46 #define ISMETA(x)	((x) & 0x80)
47 #define UNMETA(x)	((x) & 0x7F)
48 #if	!defined(HIST_SIZE)
49 #define HIST_SIZE	20
50 #endif	/* !defined(HIST_SIZE) */
51 
52 /*
53 **  Command status codes.
54 */
55 typedef enum _STATUS {
56     CSdone, CSeof, CSmove, CSdispatch, CSstay, CSsignal
57 } STATUS;
58 
59 /*
60 **  The type of case-changing to perform.
61 */
62 typedef enum _CASE {
63     TOupper, TOlower
64 } CASE;
65 
66 /*
67 **  Key to command mapping.
68 */
69 typedef struct _KEYMAP {
70     CHAR	Key;
71     STATUS	(*Function)();
72 } KEYMAP;
73 
74 /*
75 **  Command history structure.
76 */
77 typedef struct _HISTORY {
78     int		Size;
79     int		Pos;
80     CHAR	*Lines[HIST_SIZE];
81 } HISTORY;
82 
83 /*
84 **  Globals.
85 */
86 unsigned	rl_eof;
87 unsigned	rl_erase;
88 unsigned	rl_intr;
89 unsigned	rl_kill;
90 unsigned	rl_quit;
91 
92 STATIC CHAR		NIL[] = "";
93 STATIC CONST CHAR	*Input = NIL;
94 STATIC CHAR		*Line;
95 STATIC CONST char	*Prompt;
96 STATIC CHAR		*Yanked;
97 STATIC char		*Screen;
98 STATIC CONST char	NEWLINE[]= CRLF;
99 STATIC HISTORY		H;
100 STATIC int		Repeat;
101 STATIC int		End;
102 STATIC int		Mark;
103 STATIC int		OldPoint;
104 STATIC int		Point;
105 STATIC int		PushBack;
106 STATIC int		Pushed;
107 STATIC int		Signal;
108 FORWARD CONST KEYMAP	Map[32];
109 FORWARD CONST KEYMAP	MetaMap[16];
110 STATIC SIZE_T		Length;
111 STATIC SIZE_T		ScreenCount;
112 STATIC SIZE_T		ScreenSize;
113 STATIC char		*backspace;
114 STATIC int		TTYwidth;
115 STATIC int		TTYrows;
116 
117 /* Display print 8-bit chars as `M-x' or as the actual 8-bit char? */
118 int		rl_meta_chars = 0;
119 
120 /*
121 **  Declarations.
122 */
123 STATIC CHAR	*editinput();
124 #if	defined(USE_TERMCAP)
125 #include <stdlib.h>
126 #include <curses.h>
127 #include <term.h>
128 #endif	/* defined(USE_TERMCAP) */
129 
130 /*
131 **  TTY input/output functions.
132 */
133 
134 STATIC void
TTYflush()135 TTYflush()
136 {
137     if (ScreenCount) {
138         /* Dummy assignment avoids GCC warning on
139          * "attribute warn_unused_result" */
140 	ssize_t dummy = write(1, Screen, ScreenCount);
141         (void)dummy;
142 	ScreenCount = 0;
143     }
144 }
145 
146 STATIC void
TTYput(c)147 TTYput(c)
148     CHAR	c;
149 {
150     Screen[ScreenCount] = c;
151     if (++ScreenCount >= ScreenSize - 1) {
152 	ScreenSize += SCREEN_INC;
153 	RENEW(Screen, char, ScreenSize);
154     }
155 }
156 
157 STATIC void
TTYputs(p)158 TTYputs(p)
159     CONST CHAR	*p;
160 {
161     while (*p)
162 	TTYput(*p++);
163 }
164 
165 STATIC void
TTYshow(c)166 TTYshow(c)
167     CHAR	c;
168 {
169     if (c == DEL) {
170 	TTYput('^');
171 	TTYput('?');
172     }
173     else if (ISCTL(c)) {
174 	TTYput('^');
175 	TTYput(UNCTL(c));
176     }
177     else if (rl_meta_chars && ISMETA(c)) {
178 	TTYput('M');
179 	TTYput('-');
180 	TTYput(UNMETA(c));
181     }
182     else
183 	TTYput(c);
184 }
185 
186 STATIC void
TTYstring(p)187 TTYstring(p)
188     CHAR	*p;
189 {
190     while (*p)
191 	TTYshow(*p++);
192 }
193 
194 STATIC unsigned int
TTYget()195 TTYget()
196 {
197     CHAR	c;
198 
199     TTYflush();
200     if (Pushed) {
201 	Pushed = 0;
202 	return PushBack;
203     }
204     if (*Input)
205 	return *Input++;
206     return read(0, &c, (SIZE_T)1) == 1 ? c : EOF;
207 }
208 
209 #define TTYback()	(backspace ? TTYputs((CHAR *)backspace) : TTYput('\b'))
210 
211 STATIC void
TTYbackn(n)212 TTYbackn(n)
213     int		n;
214 {
215     while (--n >= 0)
216 	TTYback();
217 }
218 
219 STATIC void
TTYinfo()220 TTYinfo()
221 {
222     static int		init;
223 #if	defined(USE_TERMCAP)
224     char		*term;
225     char		buff[2048];
226     char		*bp, *p;
227 #endif	/* defined(USE_TERMCAP) */
228 #if	defined(TIOCGWINSZ)
229     struct winsize	W;
230 #endif	/* defined(TIOCGWINSZ) */
231 
232     if (init) {
233 #if	defined(TIOCGWINSZ)
234 	/* Perhaps we got resized. */
235 	if (ioctl(0, TIOCGWINSZ, &W) >= 0
236 	 && W.ws_col > 0 && W.ws_row > 0) {
237 	    TTYwidth = (int)W.ws_col;
238 	    TTYrows = (int)W.ws_row;
239 	}
240 #endif	/* defined(TIOCGWINSZ) */
241 	return;
242     }
243     init++;
244 
245     TTYwidth = TTYrows = 0;
246 #if	defined(USE_TERMCAP)
247     bp = &buff[0];
248     if ((term = getenv("TERM")) == NULL)
249 	term = "dumb";
250     if (tgetent(buff, term) < 0) {
251        TTYwidth = SCREEN_WIDTH;
252        TTYrows = SCREEN_ROWS;
253        return;
254     }
255     p = tgetstr("le", &bp);
256     backspace = p ? strdup(p) : NULL;
257     TTYwidth = tgetnum("co");
258     TTYrows = tgetnum("li");
259 #endif	/* defined(USE_TERMCAP) */
260 
261 #if	defined(TIOCGWINSZ)
262     if (ioctl(0, TIOCGWINSZ, &W) >= 0) {
263 	TTYwidth = (int)W.ws_col;
264 	TTYrows = (int)W.ws_row;
265     }
266 #endif	/* defined(TIOCGWINSZ) */
267 
268     if (TTYwidth <= 0 || TTYrows <= 0) {
269 	TTYwidth = SCREEN_WIDTH;
270 	TTYrows = SCREEN_ROWS;
271     }
272 }
273 
274 
275 STATIC void
reposition()276 reposition()
277 {
278     int		i;
279     CHAR	*p;
280 
281     TTYput('\r');
282     TTYputs((CONST CHAR *)Prompt);
283     for (i = Point, p = Line; --i >= 0; p++)
284 	TTYshow(*p);
285 }
286 
287 STATIC void
left(Change)288 left(Change)
289     STATUS	Change;
290 {
291     TTYback();
292     if (Point) {
293 	if (ISCTL(Line[Point - 1]))
294 	    TTYback();
295         else if (rl_meta_chars && ISMETA(Line[Point - 1])) {
296 	    TTYback();
297 	    TTYback();
298 	}
299     }
300     if (Change == CSmove)
301 	Point--;
302 }
303 
304 STATIC void
right(Change)305 right(Change)
306     STATUS	Change;
307 {
308     TTYshow(Line[Point]);
309     if (Change == CSmove)
310 	Point++;
311 }
312 
313 STATIC STATUS
ring_bell()314 ring_bell()
315 {
316     TTYput('\07');
317     TTYflush();
318     return CSstay;
319 }
320 
321 STATIC STATUS
do_macro(c)322 do_macro(c)
323     unsigned int	c;
324 {
325     CHAR		name[4];
326 
327     name[0] = '_';
328     name[1] = c;
329     name[2] = '_';
330     name[3] = '\0';
331 
332     if ((Input = (CHAR *)getenv((char *)name)) == NULL) {
333 	Input = NIL;
334 	return ring_bell();
335     }
336     return CSstay;
337 }
338 
339 STATIC STATUS
do_forward(move)340 do_forward(move)
341     STATUS	move;
342 {
343     int		i;
344     CHAR	*p;
345 
346     i = 0;
347     do {
348 	p = &Line[Point];
349 	for ( ; Point < End && (*p == ' ' || !isalnum(*p)); Point++, p++)
350 	    if (move == CSmove)
351 		right(CSstay);
352 
353 	for (; Point < End && isalnum(*p); Point++, p++)
354 	    if (move == CSmove)
355 		right(CSstay);
356 
357 	if (Point == End)
358 	    break;
359     } while (++i < Repeat);
360 
361     return CSstay;
362 }
363 
364 STATIC STATUS
do_case(type)365 do_case(type)
366     CASE	type;
367 {
368     int		i;
369     int		end;
370     int		count;
371     CHAR	*p;
372 
373     (void)do_forward(CSstay);
374     if (OldPoint != Point) {
375 	if ((count = Point - OldPoint) < 0)
376 	    count = -count;
377 	Point = OldPoint;
378 	if ((end = Point + count) > End)
379 	    end = End;
380 	for (i = Point, p = &Line[i]; i < end; i++, p++) {
381 	    if (type == TOupper) {
382 		if (islower(*p))
383 		    *p = toupper(*p);
384 	    }
385 	    else if (isupper(*p))
386 		*p = tolower(*p);
387 	    right(CSmove);
388 	}
389     }
390     return CSstay;
391 }
392 
393 STATIC STATUS
case_down_word()394 case_down_word()
395 {
396     return do_case(TOlower);
397 }
398 
399 STATIC STATUS
case_up_word()400 case_up_word()
401 {
402     return do_case(TOupper);
403 }
404 
405 STATIC void
ceol()406 ceol()
407 {
408     int		extras;
409     int		i;
410     CHAR	*p;
411 
412     for (extras = 0, i = Point, p = &Line[i]; i <= End; i++, p++) {
413 	TTYput(' ');
414 	if (ISCTL(*p)) {
415 	    TTYput(' ');
416 	    extras++;
417 	}
418 	else if (rl_meta_chars && ISMETA(*p)) {
419 	    TTYput(' ');
420 	    TTYput(' ');
421 	    extras += 2;
422 	}
423     }
424 
425     for (i += extras; i > Point; i--)
426 	TTYback();
427 }
428 
429 STATIC void
clear_line()430 clear_line()
431 {
432     Point = -strlen(Prompt);
433     TTYput('\r');
434     ceol();
435     Point = 0;
436     End = 0;
437     Line[0] = '\0';
438 }
439 
440 STATIC STATUS
insert_string(p)441 insert_string(p)
442     CHAR	*p;
443 {
444     SIZE_T	len;
445     int		i;
446     CHAR	*new;
447     CHAR	*q;
448 
449     len = strlen((char *)p);
450     if (End + len >= Length) {
451 	if ((new = NEW(CHAR, Length + len + MEM_INC)) == NULL)
452 	    return CSstay;
453 	if (Length) {
454 	    COPYFROMTO(new, Line, Length);
455 	    DISPOSE(Line);
456 	}
457 	Line = new;
458 	Length += len + MEM_INC;
459     }
460 
461     for (q = &Line[Point], i = End - Point; --i >= 0; )
462 	q[len + i] = q[i];
463     COPYFROMTO(&Line[Point], p, len);
464     End += len;
465     Line[End] = '\0';
466     TTYstring(&Line[Point]);
467     Point += len;
468 
469     return Point == End ? CSstay : CSmove;
470 }
471 
472 STATIC STATUS
redisplay()473 redisplay()
474 {
475     TTYputs((CONST CHAR *)NEWLINE);
476     TTYputs((CONST CHAR *)Prompt);
477     TTYstring(Line);
478     return CSmove;
479 }
480 
481 STATIC STATUS
toggle_meta_mode()482 toggle_meta_mode()
483 {
484     rl_meta_chars = ! rl_meta_chars;
485     return redisplay();
486 }
487 
488 
489 STATIC CHAR *
next_hist()490 next_hist()
491 {
492     return H.Pos >= H.Size - 1 ? NULL : H.Lines[++H.Pos];
493 }
494 
495 STATIC CHAR *
prev_hist()496 prev_hist()
497 {
498     return H.Pos == 0 ? NULL : H.Lines[--H.Pos];
499 }
500 
501 STATIC STATUS
do_insert_hist(p)502 do_insert_hist(p)
503     CHAR	*p;
504 {
505     if (p == NULL)
506 	return ring_bell();
507     Point = 0;
508     reposition();
509     ceol();
510     End = 0;
511     return insert_string(p);
512 }
513 
514 STATIC STATUS
515 do_hist(move)
516     CHAR	*(*move)();
517 {
518     CHAR	*p;
519     int		i;
520 
521     i = 0;
522     do {
523 	if ((p = (*move)()) == NULL)
524 	    return ring_bell();
525     } while (++i < Repeat);
526     return do_insert_hist(p);
527 }
528 
529 STATIC STATUS
h_next()530 h_next()
531 {
532     return do_hist(next_hist);
533 }
534 
535 STATIC STATUS
h_prev()536 h_prev()
537 {
538     return do_hist(prev_hist);
539 }
540 
541 STATIC STATUS
h_first()542 h_first()
543 {
544     return do_insert_hist(H.Lines[H.Pos = 0]);
545 }
546 
547 STATIC STATUS
h_last()548 h_last()
549 {
550     return do_insert_hist(H.Lines[H.Pos = H.Size - 1]);
551 }
552 
553 /*
554 **  Return zero if pat appears as a substring in text.
555 */
556 STATIC int
substrcmp(text,pat,len)557 substrcmp(text, pat, len)
558     char	*text;
559     char	*pat;
560     int		len;
561 {
562     char	c;
563 
564     if ((c = *pat) == '\0')
565         return *text == '\0';
566     for ( ; *text; text++)
567         if (*text == c && strncmp(text, pat, len) == 0)
568             return 0;
569     return 1;
570 }
571 
572 STATIC CHAR *
search_hist(search,move)573 search_hist(search, move)
574     CHAR	*search;
575     CHAR	*(*move)();
576 {
577     static CHAR	*old_search;
578     int		len;
579     int		pos;
580     int		(*match)();
581     char	*pat;
582 
583     /* Save or get remembered search pattern. */
584     if (search && *search) {
585 	if (old_search)
586 	    DISPOSE(old_search);
587 	old_search = (CHAR *)strdup((char *)search);
588     }
589     else {
590 	if (old_search == NULL || *old_search == '\0')
591             return NULL;
592 	search = old_search;
593     }
594 
595     /* Set up pattern-finder. */
596     if (*search == '^') {
597 	match = strncmp;
598 	pat = (char *)(search + 1);
599     }
600     else {
601 	match = substrcmp;
602 	pat = (char *)search;
603     }
604     len = strlen(pat);
605 
606     for (pos = H.Pos; (*move)() != NULL; )
607 	if ((*match)((char *)H.Lines[H.Pos], pat, len) == 0)
608             return H.Lines[H.Pos];
609     H.Pos = pos;
610     return NULL;
611 }
612 
613 STATIC STATUS
h_search()614 h_search()
615 {
616     static int	Searching;
617     CONST char	*old_prompt;
618     CHAR	*(*move)();
619     CHAR	*p;
620 
621     if (Searching)
622 	return ring_bell();
623     Searching = 1;
624 
625     clear_line();
626     old_prompt = Prompt;
627     Prompt = "Search: ";
628     TTYputs((CONST CHAR *)Prompt);
629     move = Repeat == NO_ARG ? prev_hist : next_hist;
630     p = editinput();
631     Prompt = old_prompt;
632     Searching = 0;
633     TTYputs((CONST CHAR *)Prompt);
634     if (p == NULL && Signal > 0) {
635 	Signal = 0;
636 	clear_line();
637 	return redisplay();
638     }
639     p = search_hist(p, move);
640     clear_line();
641     if (p == NULL) {
642 	(void)ring_bell();
643 	return redisplay();
644     }
645     return do_insert_hist(p);
646 }
647 
648 STATIC STATUS
fd_char()649 fd_char()
650 {
651     int		i;
652 
653     i = 0;
654     do {
655 	if (Point >= End)
656 	    break;
657 	right(CSmove);
658     } while (++i < Repeat);
659     return CSstay;
660 }
661 
662 STATIC void
save_yank(begin,i)663 save_yank(begin, i)
664     int		begin;
665     int		i;
666 {
667     if (Yanked) {
668 	DISPOSE(Yanked);
669 	Yanked = NULL;
670     }
671 
672     if (i < 1)
673 	return;
674 
675     if ((Yanked = NEW(CHAR, (SIZE_T)i + 1)) != NULL) {
676 	COPYFROMTO(Yanked, &Line[begin], i);
677 	Yanked[i] = '\0';
678     }
679 }
680 
681 STATIC STATUS
delete_string(count)682 delete_string(count)
683     int		count;
684 {
685     int		i;
686     CHAR	*p;
687 
688     if (count <= 0 || End == Point)
689 	return ring_bell();
690 
691     if (count == 1 && Point == End - 1) {
692 	/* Optimize common case of delete at end of line. */
693 	End--;
694 	p = &Line[Point];
695 	i = 1;
696 	TTYput(' ');
697 	if (ISCTL(*p)) {
698 	    i = 2;
699 	    TTYput(' ');
700 	}
701 	else if (rl_meta_chars && ISMETA(*p)) {
702 	    i = 3;
703 	    TTYput(' ');
704 	    TTYput(' ');
705 	}
706 	TTYbackn(i);
707 	*p = '\0';
708 	return CSmove;
709     }
710     if (Point + count > End && (count = End - Point) <= 0)
711 	return CSstay;
712 
713     if (count > 1)
714 	save_yank(Point, count);
715 
716     for (p = &Line[Point], i = End - (Point + count) + 1; --i >= 0; p++)
717 	p[0] = p[count];
718     ceol();
719     End -= count;
720     TTYstring(&Line[Point]);
721     return CSmove;
722 }
723 
724 STATIC STATUS
bk_char()725 bk_char()
726 {
727     int		i;
728 
729     i = 0;
730     do {
731 	if (Point == 0)
732 	    break;
733 	left(CSmove);
734     } while (++i < Repeat);
735 
736     return CSstay;
737 }
738 
739 STATIC STATUS
bk_del_char()740 bk_del_char()
741 {
742     int		i;
743 
744     i = 0;
745     do {
746 	if (Point == 0)
747 	    break;
748 	left(CSmove);
749     } while (++i < Repeat);
750 
751     return delete_string(i);
752 }
753 
754 STATIC STATUS
kill_line()755 kill_line()
756 {
757     int		i;
758 
759     if (Repeat != NO_ARG) {
760 	if (Repeat < Point) {
761 	    i = Point;
762 	    Point = Repeat;
763 	    reposition();
764 	    (void)delete_string(i - Point);
765 	}
766 	else if (Repeat > Point) {
767 	    right(CSmove);
768 	    (void)delete_string(Repeat - Point - 1);
769 	}
770 	return CSmove;
771     }
772 
773     save_yank(Point, End - Point);
774     Line[Point] = '\0';
775     ceol();
776     End = Point;
777     return CSstay;
778 }
779 
780 STATIC STATUS
insert_char(c)781 insert_char(c)
782     int		c;
783 {
784     STATUS	s;
785     CHAR	buff[2];
786     CHAR	*p;
787     CHAR	*q;
788     int		i;
789 
790     if (Repeat == NO_ARG || Repeat < 2) {
791 	buff[0] = c;
792 	buff[1] = '\0';
793 	return insert_string(buff);
794     }
795 
796     if ((p = NEW(CHAR, Repeat + 1)) == NULL)
797 	return CSstay;
798     for (i = Repeat, q = p; --i >= 0; )
799 	*q++ = c;
800     *q = '\0';
801     Repeat = 0;
802     s = insert_string(p);
803     DISPOSE(p);
804     return s;
805 }
806 
807 STATIC STATUS
meta()808 meta()
809 {
810     unsigned int	c;
811     CONST KEYMAP	*kp;
812 
813     if ((int)(c = TTYget()) == EOF)
814 	return CSeof;
815 #if	defined(ANSI_ARROWS)
816     /* Also include VT-100 arrows. */
817     if (c == '[' || c == 'O')
818 	switch (c = TTYget()) {
819 	default:	return ring_bell();
820 	case EOF:	return CSeof;
821 	case 'A':	return h_prev();
822 	case 'B':	return h_next();
823 	case 'C':	return fd_char();
824 	case 'D':	return bk_char();
825 	}
826 #endif	/* defined(ANSI_ARROWS) */
827 
828     if (isdigit(c)) {
829 	for (Repeat = c - '0'; (int)(c = TTYget()) != EOF && isdigit(c); )
830 	    Repeat = Repeat * 10 + c - '0';
831 	Pushed = 1;
832 	PushBack = c;
833 	return CSstay;
834     }
835 
836     if (isupper(c))
837 	return do_macro(c);
838     for (OldPoint = Point, kp = MetaMap; kp->Function; kp++)
839 	if (kp->Key == c)
840 	    return (*kp->Function)();
841 
842     return ring_bell();
843 }
844 
845 STATIC STATUS
emacs(c)846 emacs(c)
847     unsigned int	c;
848 {
849     STATUS		s;
850     const KEYMAP	*kp;
851 
852     if (rl_meta_chars && ISMETA(c)) {
853 	Pushed = 1;
854 	PushBack = UNMETA(c);
855 	return meta();
856     }
857     for (kp = Map; kp->Function; kp++)
858 	if (kp->Key == c)
859 	    break;
860     s = kp->Function ? (*kp->Function)() : insert_char((int)c);
861     if (!Pushed)
862 	/* No pushback means no repeat count; hacky, but true. */
863 	Repeat = NO_ARG;
864     return s;
865 }
866 
867 STATIC STATUS
TTYspecial(c)868 TTYspecial(c)
869     unsigned int	c;
870 {
871     if (ISMETA(c))
872 	return CSdispatch;
873 
874     if (c == rl_erase || (int)c == DEL)
875 	return bk_del_char();
876     if (c == rl_kill) {
877 	if (Point != 0) {
878 	    Point = 0;
879 	    reposition();
880 	}
881 	Repeat = NO_ARG;
882 	return kill_line();
883     }
884     if (c == rl_eof && Point == 0 && End == 0)
885 	return CSeof;
886     if (c == rl_intr) {
887 	Signal = SIGINT;
888 	return CSsignal;
889     }
890     if (c == rl_quit) {
891 	Signal = SIGQUIT;
892 	return CSeof;
893     }
894 
895     return CSdispatch;
896 }
897 
898 STATIC CHAR *
editinput()899 editinput()
900 {
901     unsigned int	c;
902 
903     Repeat = NO_ARG;
904     OldPoint = Point = Mark = End = 0;
905     Line[0] = '\0';
906 
907     Signal = -1;
908     while ((int)(c = TTYget()) != EOF)
909 	switch (TTYspecial(c)) {
910 	case CSdone:
911 	    return Line;
912 	case CSeof:
913 	    return NULL;
914 	case CSsignal:
915 	    return (CHAR *)"";
916 	case CSmove:
917 	    reposition();
918 	    break;
919 	case CSdispatch:
920 	    switch (emacs(c)) {
921 	    case CSdone:
922 		return Line;
923 	    case CSeof:
924 		return NULL;
925 	    case CSsignal:
926 		return (CHAR *)"";
927 	    case CSmove:
928 		reposition();
929 		break;
930 	    case CSdispatch:
931 	    case CSstay:
932 		break;
933 	    }
934 	    break;
935 	case CSstay:
936 	    break;
937 	}
938     if (strlen((char *)Line))
939         return Line;
940     free(Line);
941     return NULL;
942 }
943 
944 STATIC void
hist_add(p)945 hist_add(p)
946     CHAR	*p;
947 {
948     int		i;
949 
950     if ((p = (CHAR *)strdup((char *)p)) == NULL)
951 	return;
952     if (H.Size < HIST_SIZE)
953 	H.Lines[H.Size++] = p;
954     else {
955 	DISPOSE(H.Lines[0]);
956 	for (i = 0; i < HIST_SIZE - 1; i++)
957 	    H.Lines[i] = H.Lines[i + 1];
958 	H.Lines[i] = p;
959     }
960     H.Pos = H.Size - 1;
961 }
962 
963 /*
964 **  For compatibility with FSF readline.
965 */
966 /* ARGSUSED0 */
967 void
rl_reset_terminal(p)968 rl_reset_terminal(p)
969     char	*p;
970 {
971     (void)p;
972 }
973 
974 void
rl_initialize()975 rl_initialize()
976 {
977 }
978 
979 char *
readline(prompt)980 readline(prompt)
981     CONST char	*prompt;
982 {
983     CHAR	*line;
984     int		s;
985 
986     if (Line == NULL) {
987 	Length = MEM_INC;
988 	if ((Line = NEW(CHAR, Length)) == NULL)
989 	    return NULL;
990     }
991 
992     TTYinfo();
993     rl_ttyset(0);
994     hist_add(NIL);
995     ScreenSize = SCREEN_INC;
996     Screen = NEW(char, ScreenSize);
997     Prompt = prompt ? prompt : (char *)NIL;
998     TTYputs((CONST CHAR *)Prompt);
999     if ((line = editinput()) != NULL) {
1000 	line = (CHAR *)strdup((char *)line);
1001 	TTYputs((CONST CHAR *)NEWLINE);
1002 	TTYflush();
1003     }
1004     rl_ttyset(1);
1005     DISPOSE(Screen);
1006     DISPOSE(H.Lines[--H.Size]);
1007     if (Signal > 0) {
1008 	s = Signal;
1009 	Signal = 0;
1010 	(void)kill(getpid(), s);
1011     }
1012     return (char *)line;
1013 }
1014 
1015 void
add_history(p)1016 add_history(p)
1017     char	*p;
1018 {
1019     if (p == NULL || *p == '\0')
1020 	return;
1021 
1022 #if	defined(UNIQUE_HISTORY)
1023     if (H.Size && strcmp(p, (char *)H.Lines[H.Size - 1]) == 0)
1024         return;
1025 #endif	/* defined(UNIQUE_HISTORY) */
1026     hist_add((CHAR *)p);
1027 }
1028 
1029 
1030 STATIC STATUS
beg_line()1031 beg_line()
1032 {
1033     if (Point) {
1034 	Point = 0;
1035 	return CSmove;
1036     }
1037     return CSstay;
1038 }
1039 
1040 STATIC STATUS
del_char()1041 del_char()
1042 {
1043     return delete_string(Repeat == NO_ARG ? 1 : Repeat);
1044 }
1045 
1046 STATIC STATUS
end_line()1047 end_line()
1048 {
1049     if (Point != End) {
1050 	Point = End;
1051 	return CSmove;
1052     }
1053     return CSstay;
1054 }
1055 
1056 STATIC STATUS
accept_line()1057 accept_line()
1058 {
1059     Line[End] = '\0';
1060     return CSdone;
1061 }
1062 
1063 STATIC STATUS
transpose()1064 transpose()
1065 {
1066     CHAR	c;
1067 
1068     if (Point) {
1069 	if (Point == End)
1070 	    left(CSmove);
1071 	c = Line[Point - 1];
1072 	left(CSstay);
1073 	Line[Point - 1] = Line[Point];
1074 	TTYshow(Line[Point - 1]);
1075 	Line[Point++] = c;
1076 	TTYshow(c);
1077     }
1078     return CSstay;
1079 }
1080 
1081 STATIC STATUS
quote()1082 quote()
1083 {
1084     unsigned int	c;
1085 
1086     return (int)(c = TTYget()) == EOF ? CSeof : insert_char((int)c);
1087 }
1088 
1089 STATIC STATUS
wipe()1090 wipe()
1091 {
1092     int		i;
1093 
1094     if (Mark > End)
1095 	return ring_bell();
1096 
1097     if (Point > Mark) {
1098 	i = Point;
1099 	Point = Mark;
1100 	Mark = i;
1101 	reposition();
1102     }
1103 
1104     return delete_string(Mark - Point);
1105 }
1106 
1107 STATIC STATUS
mk_set()1108 mk_set()
1109 {
1110     Mark = Point;
1111     return CSstay;
1112 }
1113 
1114 STATIC STATUS
exchange()1115 exchange()
1116 {
1117     unsigned int	c;
1118 
1119     if ((c = TTYget()) != CTL('X'))
1120 	return (int)c == EOF ? CSeof : ring_bell();
1121 
1122     if ((int)(c = Mark) <= End) {
1123 	Mark = Point;
1124 	Point = c;
1125 	return CSmove;
1126     }
1127     return CSstay;
1128 }
1129 
1130 STATIC STATUS
yank()1131 yank()
1132 {
1133     if (Yanked && *Yanked)
1134 	return insert_string(Yanked);
1135     return CSstay;
1136 }
1137 
1138 STATIC STATUS
copy_region()1139 copy_region()
1140 {
1141     if (Mark > End)
1142 	return ring_bell();
1143 
1144     if (Point > Mark)
1145 	save_yank(Mark, Point - Mark);
1146     else
1147 	save_yank(Point, Mark - Point);
1148 
1149     return CSstay;
1150 }
1151 
1152 STATIC STATUS
move_to_char()1153 move_to_char()
1154 {
1155     unsigned int	c;
1156     int			i;
1157     CHAR		*p;
1158 
1159     if ((int)(c = TTYget()) == EOF)
1160 	return CSeof;
1161     for (i = Point + 1, p = &Line[i]; i < End; i++, p++)
1162 	if (*p == c) {
1163 	    Point = i;
1164 	    return CSmove;
1165 	}
1166     return CSstay;
1167 }
1168 
1169 STATIC STATUS
fd_word()1170 fd_word()
1171 {
1172     return do_forward(CSmove);
1173 }
1174 
1175 STATIC STATUS
fd_kill_word()1176 fd_kill_word()
1177 {
1178     int		i;
1179 
1180     (void)do_forward(CSstay);
1181     if (OldPoint != Point) {
1182 	i = Point - OldPoint;
1183 	Point = OldPoint;
1184 	return delete_string(i);
1185     }
1186     return CSstay;
1187 }
1188 
1189 STATIC STATUS
bk_word()1190 bk_word()
1191 {
1192     int		i;
1193     CHAR	*p;
1194 
1195     i = 0;
1196     do {
1197 	for (p = &Line[Point]; p > Line && !isalnum(p[-1]); p--)
1198 	    left(CSmove);
1199 
1200 	for (; p > Line && p[-1] != ' ' && isalnum(p[-1]); p--)
1201 	    left(CSmove);
1202 
1203 	if (Point == 0)
1204 	    break;
1205     } while (++i < Repeat);
1206 
1207     return CSstay;
1208 }
1209 
1210 STATIC STATUS
bk_kill_word()1211 bk_kill_word()
1212 {
1213     (void)bk_word();
1214     if (OldPoint != Point)
1215 	return delete_string(OldPoint - Point);
1216     return CSstay;
1217 }
1218 
1219 STATIC int
argify(line,avp)1220 argify(line, avp)
1221     CHAR	*line;
1222     CHAR	***avp;
1223 {
1224     CHAR	*c;
1225     CHAR	**p;
1226     CHAR	**new;
1227     int		ac;
1228     int		i;
1229 
1230     i = MEM_INC;
1231     if ((*avp = p = NEW(CHAR*, i))== NULL)
1232 	 return 0;
1233 
1234     for (c = line; isspace(*c); c++)
1235 	continue;
1236     if (*c == '\n' || *c == '\0')
1237 	return 0;
1238 
1239     for (ac = 0, p[ac++] = c; *c && *c != '\n'; ) {
1240 	if (isspace(*c)) {
1241 	    *c++ = '\0';
1242 	    if (*c && *c != '\n') {
1243 		if (ac + 1 == i) {
1244 		    new = NEW(CHAR*, i + MEM_INC);
1245 		    if (new == NULL) {
1246 			p[ac] = NULL;
1247 			return ac;
1248 		    }
1249 		    COPYFROMTO(new, p, i * sizeof (char **));
1250 		    i += MEM_INC;
1251 		    DISPOSE(p);
1252 		    *avp = p = new;
1253 		}
1254 		p[ac++] = c;
1255 	    }
1256 	}
1257 	else
1258 	    c++;
1259     }
1260     *c = '\0';
1261     p[ac] = NULL;
1262     return ac;
1263 }
1264 
1265 STATIC STATUS
last_argument()1266 last_argument()
1267 {
1268     CHAR	**av;
1269     CHAR	*p;
1270     STATUS	s;
1271     int		ac;
1272 
1273     if (H.Size == 1 || (p = H.Lines[H.Size - 2]) == NULL)
1274 	return ring_bell();
1275 
1276     if ((p = (CHAR *)strdup((char *)p)) == NULL)
1277 	return CSstay;
1278     ac = argify(p, &av);
1279 
1280     if (Repeat != NO_ARG)
1281 	s = Repeat < ac ? insert_string(av[Repeat]) : ring_bell();
1282     else
1283 	s = ac ? insert_string(av[ac - 1]) : CSstay;
1284 
1285     if (ac)
1286 	DISPOSE(av);
1287     DISPOSE(p);
1288     return s;
1289 }
1290 
1291 STATIC CONST KEYMAP Map[32] = {
1292     {	CTL('@'),	ring_bell	},
1293     {	CTL('A'),	beg_line	},
1294     {	CTL('B'),	bk_char		},
1295     {	CTL('D'),	del_char	},
1296     {	CTL('E'),	end_line	},
1297     {	CTL('F'),	fd_char		},
1298     {	CTL('G'),	ring_bell	},
1299     {	CTL('H'),	bk_del_char	},
1300     {	CTL('J'),	accept_line	},
1301     {	CTL('K'),	kill_line	},
1302     {	CTL('L'),	redisplay	},
1303     {	CTL('M'),	accept_line	},
1304     {	CTL('N'),	h_next		},
1305     {	CTL('O'),	ring_bell	},
1306     {	CTL('P'),	h_prev		},
1307     {	CTL('Q'),	ring_bell	},
1308     {	CTL('R'),	h_search	},
1309     {	CTL('S'),	ring_bell	},
1310     {	CTL('T'),	transpose	},
1311     {	CTL('U'),	ring_bell	},
1312     {	CTL('V'),	quote		},
1313     {	CTL('W'),	wipe		},
1314     {	CTL('X'),	exchange	},
1315     {	CTL('Y'),	yank		},
1316     {	CTL('Z'),	ring_bell	},
1317     {	CTL('['),	meta		},
1318     {	CTL(']'),	move_to_char	},
1319     {	CTL('^'),	ring_bell	},
1320     {	CTL('_'),	ring_bell	},
1321     {	0,		NULL		}
1322 };
1323 
1324 STATIC CONST KEYMAP MetaMap[16]= {
1325     {	CTL('H'),	bk_kill_word	},
1326     {	DEL,		bk_kill_word	},
1327     {	' ',		mk_set		},
1328     {	'.',		last_argument	},
1329     {	'<',		h_first		},
1330     {	'>',		h_last		},
1331     {	'b',		bk_word		},
1332     {	'd',		fd_kill_word	},
1333     {	'f',		fd_word		},
1334     {	'l',		case_down_word	},
1335     {	'm',		toggle_meta_mode },
1336     {	'u',		case_up_word	},
1337     {	'y',		yank		},
1338     {	'w',		copy_region	},
1339     {	0,		NULL		}
1340 };
1341 
1342