1 /*	Input:	Various input routines for MicroEMACS
2 		written by Daniel Lawrence
3 		(C)Copyright 1995 by Daniel M. Lawrence
4 
5 	Notes:
6 
7 	MicroEMACS's kernel processes two distinct forms of
8 	characters.  One of these is a standard unsigned character
9 	which is used in the edited text.  The other form, called
10 	an EMACS Extended Character is a 2 byte value which contains
11 	both an ascii value, and flags for certain prefixes/events.
12 
13 	Bit	Usage
14 	---	-----
15 	0 -> 7	Standard 8 bit ascii character
16 	8	Control key flag
17 	9	META prefix flag
18 	10	^X prefix flag
19 	11	Function key flag
20 	12	Mouse prefix
21 	13	Shifted flag (not needed on alpha shifted characters)
22 	14	Alterate prefix (ALT key on PCs)
23 
24 	The machine dependent driver is responsible for returning
25 	a byte stream from the various input devices with various
26 	prefixes/events embedded as escape codes.  Zero is used as the
27 	value indicating an escape sequence is next.  The format of
28 	an escape sequence is as follows:
29 
30 	0		Escape indicator
31 	<prefix byte>	upper byte of extended character
32 	{<col><row>}	col, row position if the prefix byte
33 			indicated a mouse event or a menu selection
34 			in which case these form a 16 bit menu ID
35 	<event code>	value of event
36 
37 	A ^<space> sequence (0/1/32) is generated when an actual
38 	null is being input from the control-space key under many
39 	unix systems.  These values are then interpreted by get_key()
40 	to construct the proper extended character sequences to pass
41 	to the MicroEMACS kernel.
42 */
43 
44 #include	<stdio.h>
45 #include	"estruct.h"
46 #include	"eproto.h"
47 #include	"edef.h"
48 #include	"elang.h"
49 
50 #if USG | AIX | AUX | BSD | FREEBSD | SUN | HPUX8 | HPUX9
51 #include	<pwd.h>
52 extern struct passwd *getpwnam();
53 #endif
54 
55 /*
56  * Ask a yes or no question in the message line. Return either TRUE, FALSE, or
57  * ABORT. The ABORT status is returned if the user bumps out of the question
58  * with a ^G. Used any time a confirmation is required.
59  */
60 
61 #if	!WINDOW_MSWIN	/* for MS Windows, mlyesno is defined in mswsys.c */
mlyesno(prompt)62 PASCAL NEAR mlyesno(prompt)
63 
64 char *prompt;
65 
66 {
67 	int  c;			/* input character */
68 	char buf[NPAT];		/* prompt to user */
69 
70 	for (;;) {
71 		/* build and prompt the user */
72 		strcpy(buf, prompt);
73 		strcat(buf, TEXT162);
74 /*                          " [y/n]? " */
75 		mlwrite(buf);
76 
77 		/* get the response */
78 	        c = getcmd();   /* getcmd() lets us check for anything that might */
79 	                        /* generate a 'y' or 'Y' in case use screws up */
80 
81 		if (c == ectoc(abortc))		/* Bail out! */
82 			return(ABORT);
83 
84 	        if  ((c == 'n') || (c == 'N')
85         	    || (c & (SPEC|ALTD|CTRL|META|CTLX|MOUS)))
86                 	return(FALSE);  /* ONLY 'y' or 'Y' allowed!!! */
87 
88 #if	FRENCH
89 		if (c=='o' || c=='O')
90 			return(TRUE);
91 #endif
92 
93 		if (c=='y' || c=='Y')
94 			return(TRUE);
95 
96 		return(FALSE);
97 	}
98 }
99 #endif
100 
101 /*
102  * Write a prompt into the message line, then read back a response. Keep
103  * track of the physical position of the cursor. If we are in a keyboard
104  * macro throw the prompt away, and return the remembered response. This
105  * lets macros run at full speed. The reply is always terminated by a carriage
106  * return. Handle erase, kill, and abort keys.
107  */
108 
mlreply(prompt,buf,nbuf)109 PASCAL NEAR mlreply(prompt, buf, nbuf)
110 
111 char *prompt;
112 char *buf;
113 int nbuf;
114 
115 {
116 	return(nextarg(prompt, buf, nbuf, ctoec((int) '\r')));
117 }
118 
119 /*	ectoc:	expanded character to character
120 		collapse the CTRL and SPEC flags back into an ascii code   */
121 
ectoc(c)122 PASCAL NEAR ectoc(c)
123 
124 int c;
125 
126 {
127 	if (c == (CTRL | ' '))
128 		c = 0;
129 	if (c & CTRL)
130 		c = c ^ (CTRL | 0x40);
131 	if (c & SPEC)
132 		c = c & 255;
133 	return(c);
134 }
135 
136 /*	ctoec:	character to extended character
137 		pull out the CTRL and SPEC prefixes (if possible)	*/
138 
ctoec(c)139 PASCAL NEAR ctoec(c)
140 
141 int c;
142 
143 {
144 	if (c == 0)
145 		c = CTRL | ' ';
146 	else if ((c >= 0x00 && c <= 0x1F) || c == 0x7F)
147                 c = CTRL | (c ^ 0x40);
148         return(c);
149 }
150 
151 /* get a command name from the command line. Command completion means
152    that pressing a <SPACE> will attempt to complete an unfinished command
153    name if it is unique.
154 */
155 
156 #if	MSC
157 int (PASCAL NEAR *PASCAL NEAR getname(char *prompt))(void)
158 #else
159 int (PASCAL NEAR *PASCAL NEAR getname(prompt))()
160 
161 char *prompt;	/* string to prompt with */
162 #endif
163 
164 {
165 	char *sp;	/* ptr to the returned string */
166 
167 	sp = complete(prompt, NULL, CMP_COMMAND, NSTRING);
168 	if (sp == NULL)
169 		return(NULL);
170 
171 	return(fncmatch(sp));
172 }
173 
174 /*	getcbuf:	get a completion from the user for a buffer name.
175 
176 			I was goaded into this by lots of other people's
177 			completion code.
178 */
179 
getcbuf(prompt,defval,createflag)180 BUFFER *PASCAL NEAR getcbuf(prompt, defval, createflag)
181 
182 char *prompt;		/* prompt to user on command line */
183 char *defval;		/* default value to display to user */
184 int createflag;		/* should this create a new buffer? */
185 
186 {
187 	char *sp;	/* ptr to the returned string */
188 
189 	sp = complete(prompt, defval, CMP_BUFFER, NBUFN);
190 	if (sp == NULL)
191 		return(NULL);
192 
193 	return(bfind(sp, createflag, 0));
194 }
195 
gtfilename(prompt)196 char *PASCAL NEAR gtfilename(prompt)
197 
198 char *prompt;		/* prompt to user on command line */
199 
200 {
201 #if	MSDOS | OS2
202 	char *scan;
203 #endif
204 #if	WINDOW_MSWIN
205 	static char sp[NFILEN];
206 
207 	if (!FILENAMEREPLY(prompt, sp, NFILEN))
208 		return NULL;
209 #else
210 	char *sp;	/* ptr to the returned string */
211 
212 	/* get a file name, default to current buffer's */
213 	if (curbp && curbp->b_fname && strcmp(curbp->b_fname, "") != 0)
214 		sp = complete(prompt, curbp->b_fname, CMP_FILENAME, NFILEN);
215 	else
216 		sp = complete(prompt, NULL, CMP_FILENAME, NFILEN);
217 #endif
218 #if	MSDOS | OS2
219 	/* change forward slashes to back */
220 	if (sp) {
221 		scan = sp;
222 		while (*scan) {
223 			if (*scan == '/')
224 				*scan = DIRSEPCHAR;
225 			++scan;
226 		}
227 	}
228 #endif
229 	return(sp);
230 }
231 
complete(prompt,defval,type,maxlen)232 char *PASCAL NEAR complete(prompt, defval, type, maxlen)
233 
234 char *prompt;		/* prompt to user on command line */
235 char *defval;		/* default value to display to user */
236 int type;		/* type of what we are completing */
237 int maxlen;		/* maximum length of input field */
238 
239 {
240 	register int c;		/* current input character */
241 	register int ec;	/* extended input character */
242 	int cpos;		/* current column on screen output */
243 	char *home_ptr;		/* pointer to home directory string */
244 	char *ptr;		/* string pointer */
245 	char user_name[NSTRING]; /* user name for directory */
246 	static char buf[NSTRING];/* buffer to hold tentative name */
247 #if USG | AIX | AUX | BSD | FREEBSD | SUN | HPUX8 | HPUX9
248 	struct passwd *pwd;	/* password structure */
249 #endif
250 
251 	/* if we are executing a command line get the next arg and match it */
252 	if (clexec) {
253 		if (macarg(buf) != TRUE)
254 			return(NULL);
255 		return(buf);
256 	}
257 
258 	/* starting at the beginning of the string buffer */
259 	cpos = 0;
260 
261 	/* if it exists, prompt the user for a buffer name */
262 	if (prompt)
263 		if (type == CMP_COMMAND)
264 			mlwrite("%s", prompt);
265 		else if (defval)
266 			mlwrite("%s[%s]: ", prompt, defval);
267 		else
268 			mlwrite("%s: ", prompt);
269 
270 	/* build a name string from the keyboard */
271 	while (TRUE) {
272 
273 		/* get the keystroke and decode it */
274 		ec = get_key();
275 		c = ectoc(ec);
276 
277 		/* if it is from the mouse, or is a function key, blow it off */
278 		if ((ec & MOUS) || (ec & SPEC))
279 			continue;
280 
281 		/* if we are at the end, just match it */
282 		if (c == '\n'  ||  c == '\r') {
283 			if (defval && cpos==0)
284 				return(defval);
285 			else {
286 				buf[cpos] = 0;
287 				return(buf);
288 			}
289 
290 		} else if (ec == abortc) {	/* Bell, abort */
291 			ctrlg(FALSE, 0);
292 			TTflush();
293 			return(NULL);
294 
295 		} else if (c == 0x7F || c == 0x08) {	/* rubout/erase */
296 			if (cpos != 0) {
297 				mlout('\b');
298 				mlout(' ');
299 				mlout('\b');
300 				--ttcol;
301 				--cpos;
302 				TTflush();
303 			}
304 
305 		} else if (c == 0x15) {	/* C-U, kill */
306 			while (cpos != 0) {
307 				mlout('\b');
308 				mlout(' ');
309 				mlout('\b');
310 				--cpos;
311 				--ttcol;
312 			}
313 			TTflush();
314 
315 		} else if ((c == ' ') || (ec == sterm) || (c == '\t')) {
316 			/* attempt a completion */
317 			switch (type) {
318 				case CMP_BUFFER:
319 					comp_buffer(buf, &cpos);
320 					break;
321 				case CMP_COMMAND:
322 					comp_command(buf, &cpos);
323 					break;
324 #if	!WINDOW_MSWIN
325 				case CMP_FILENAME:
326 					comp_file(buf, &cpos);
327 					break;
328 #endif
329 			}
330 
331 			TTflush();
332 			if (cpos > 0 && buf[cpos - 1] == 0)
333 				return(buf);
334 			goto clist;
335 
336 #if	ENVFUNC
337 		} else if ((cpos > 0) &&
338 			   ((char)c == DIRSEPCHAR) &&
339 			   (type == CMP_FILENAME) &&
340 			   (buf[0] == '~') &&
341 			   ((home_ptr = getenv("HOME")) != (char *)NULL)) {
342 
343 			/* save the user name! */
344 			buf[cpos] = 0;
345 			strcpy(user_name, &buf[1]);
346 
347 			/* erase the chars on-screen */
348 			while (cpos > 0) {
349 				mlout('\b');
350 				mlout(' ');
351 				mlout('\b');
352 				--cpos;
353 				--ttcol;
354 			}
355 
356 #if USG | AIX | AUX | BSD | FREEBSD | SUN | HPUX8 | HPUX9
357 			/* lookup someone else's home directory! */
358 			if (user_name[0] != 0) {
359 				pwd = getpwnam(user_name);
360 				if (pwd != (struct passwd *)NULL) {
361 					ptr = pwd->pw_dir;
362 					while (*ptr) {
363 						mlout(*ptr);
364 						buf[cpos++] = *ptr++;
365 						++ttcol;
366 					}
367 				}
368 			}
369 #endif
370 			if (cpos == 0) {
371 
372 				/* output the home directory */
373 				ptr = home_ptr;
374 				while (*ptr) {
375 					mlout(*ptr);
376 					buf[cpos++] = *ptr++;
377 					++ttcol;
378 				}
379 
380 				/* is this someone else's home directory */
381 				if (user_name[0] != 0) {
382 
383 					/* backup to the last directory sep */
384 					while ((cpos > 0) &&
385 					       (buf[cpos-1] != DIRSEPCHAR)) {
386 						mlout('\b');
387 						mlout(' ');
388 						mlout('\b');
389 						--cpos;
390 						--ttcol;
391 					}
392 
393 					/* and add the user's name */
394 					ptr = user_name;
395 					while (*ptr) {
396 						mlout(*ptr);
397 						buf[cpos++] = *ptr++;
398 						++ttcol;
399 					}
400 				}
401 
402 			}
403 
404 			/* and the last directory seperator */
405 			if (buf[cpos-1] != DIRSEPCHAR) {
406 				mlout(DIRSEPCHAR);
407 				buf[cpos++] = DIRSEPCHAR;
408 				++ttcol;
409 			}
410 			TTflush();
411 
412 		} else if ((cpos > 1) &&
413 			   ((char)c == DIRSEPCHAR) &&
414 			   (type == CMP_FILENAME) &&
415 			   (buf[0] == '$')) {
416 
417 			/* expand an environment variable reference */
418 			/* save the variable name! */
419 			buf[cpos] = 0;
420 			strcpy(user_name, &buf[1]);
421 #if	MSDOS | OS2 | VMS
422 			mkupper(user_name);
423 #endif
424 
425 			/* erase the chars on-screen */
426 			while (cpos > 0) {
427 				mlout('\b');
428 				mlout(' ');
429 				mlout('\b');
430 				--cpos;
431 				--ttcol;
432 			}
433 
434 			ptr = getenv(user_name);
435 			if (ptr != (char *)NULL) {
436 				while (*ptr) {
437 					mlout(*ptr);
438 					buf[cpos++] = *ptr++;
439 					++ttcol;
440 				}
441 			}
442 
443 			/* and the last directory seperator */
444 			if (buf[cpos-1] != DIRSEPCHAR) {
445 				mlout(DIRSEPCHAR);
446 				buf[cpos++] = DIRSEPCHAR;
447 				++ttcol;
448 			}
449 			TTflush();
450 
451 #endif	/* ENVFUNC */
452 
453 		} else if (c == '?') {
454 
455 clist:			/* make a completion list! */
456 			switch (type) {
457 				case CMP_BUFFER:
458 					clist_buffer(buf, &cpos);
459 					break;
460 				case CMP_COMMAND:
461 					clist_command(buf, &cpos);
462 					break;
463 #if	!WINDOW_MSWIN
464 				case CMP_FILENAME:
465 					clist_file(buf, &cpos);
466 					break;
467 #endif
468 			}
469 			update(TRUE);
470 
471 			/* if it exists, reprompt the user */
472 			if (prompt) {
473 				buf[cpos] = 0;
474 				if (type == CMP_COMMAND)
475 					mlwrite("%s%s", prompt, buf);
476 				else if (defval)
477 					mlwrite("%s[%s]: %s", prompt, defval, buf);
478 				else
479 					mlwrite("%s: %s", prompt, buf);
480 			}
481 
482 		} else {
483 			if (cpos < maxlen && c > ' ') {
484 				buf[cpos++] = c;
485 				mlout(c);
486 				++ttcol;
487 				TTflush();
488 			}
489 		}
490 	}
491 }
492 
493 /*	comp_command:	Attempt a completion on a command name	*/
494 
comp_command(name,cpos)495 VOID PASCAL NEAR comp_command(name, cpos)
496 
497 char *name;	/* command containing the current name to complete */
498 int *cpos;	/* ptr to position of next character to insert */
499 
500 {
501 	register NBIND *bp;	/* trial command to complete */
502 	register int index;	/* index into strings to compare */
503 	register int curbind;	/* index into the names[] array */
504 	register NBIND *match;	/* last command that matches string */
505 	register int matchflag;	/* did this command name match? */
506 	register int comflag;	/* was there a completion at all? */
507 
508 	/* everything (or nothing) matches an empty string */
509 	if (*cpos == 0)
510 		return;
511 
512 	/* start attempting completions, one character at a time */
513 	comflag = FALSE;
514 	curbind = 0;
515 	while (*cpos < NSTRING) {
516 
517 		/* first, we start at the first command and scan the list */
518 		match = NULL;
519 		curbind = 0;
520 		while (curbind <= numfunc) {
521 
522 			/* is this a match? */
523 			bp = &names[curbind];
524 			matchflag = TRUE;
525 			for (index = 0; index < *cpos; index++)
526 				if (name[index] != bp->n_name[index]) {
527 					matchflag = FALSE;
528 					break;
529 				}
530 
531 			/* if it is a match */
532 			if (matchflag) {
533 
534 				/* if this is the first match, simply record it */
535 				if (match == NULL) {
536 					match = bp;
537 					name[*cpos] = bp->n_name[*cpos];
538 				} else {
539 					/* if there's a difference, stop here */
540 					if (name[*cpos] != bp->n_name[*cpos])
541 						return;
542 				}
543 			}
544 
545 			/* on to the next command */
546 			curbind++;
547 		}
548 
549 		/* with no match, we are done */
550 		if (match == NULL) {
551 			/* beep if we never matched */
552 			if (comflag == FALSE)
553 				TTbeep();
554 			return;
555 		}
556 
557 		/* if we have completed all the way... go back */
558 		if (name[*cpos] == 0) {
559 			(*cpos)++;
560 			return;
561 		}
562 
563 		/* remember we matched, and complete one character */
564 		comflag = TRUE;
565 		TTputc(name[(*cpos)++]);
566 		TTflush();
567 	}
568 
569 	/* don't allow a completion past the end of the max command name length */
570 	return;
571 }
572 
573 /*	clist_command:	Make a completion list based on a partial name */
574 
clist_command(name,cpos)575 VOID PASCAL NEAR clist_command(name, cpos)
576 
577 char *name;	/* command containing the current name to complete */
578 int *cpos;	/* ptr to position of next character to insert */
579 
580 {
581 	register NBIND *bp;	/* trial command to complete */
582 	register int curbind;	/* index into the names[] array */
583 	register int name_len;	/* current length of input string */
584 	register BUFFER *listbuf;/* buffer to put completion list into */
585 
586 	/* get a buffer for the completion list */
587 	listbuf = bfind("[Completion list]", TRUE, BFINVS);
588 	if (listbuf == NULL || bclear(listbuf) == FALSE) {
589 		ctrlg(FALSE, 0);
590 		TTflush();
591 		return;
592 	}
593 
594 	name_len = *cpos;
595 
596 	/* first, we start at the first command and scan the list */
597 	for (curbind = 0; curbind <= numfunc; curbind++) {
598 
599 		/* is this a match? */
600 		bp = &names[curbind];
601 		if (strncmp(name, bp->n_name, name_len) == 0)
602 			addline(listbuf, bp->n_name);
603 	}
604 
605 	wpopup(listbuf);
606 	return;
607 }
608 
609 /*	comp_buffer:	Attempt a completion on a buffer name	*/
610 
comp_buffer(name,cpos)611 VOID PASCAL NEAR comp_buffer(name, cpos)
612 
613 char *name;	/* buffer containing the current name to complete */
614 int *cpos;	/* ptr to position of next character to insert */
615 
616 {
617 	register BUFFER *bp;	/* trial buffer to complete */
618 	register int index;	/* index into strings to compare */
619 	register BUFFER *match;	/* last buffer that matches string */
620 	register int matchflag;	/* did this buffer name match? */
621 	register int comflag;	/* was there a completion at all? */
622 
623 	/* everything (or nothing) matches an empty string */
624 	if (*cpos == 0)
625 		return;
626 
627 	/* start attempting completions, one character at a time */
628 	comflag = FALSE;
629 	while (*cpos < NBUFN) {
630 
631 		/* first, we start at the first buffer and scan the list */
632 		match = NULL;
633 		bp = bheadp;
634 		while (bp) {
635 
636 			/* is this a match? */
637 			matchflag = TRUE;
638 			for (index = 0; index < *cpos; index++)
639 				if (name[index] != bp->b_bname[index]) {
640 					matchflag = FALSE;
641 					break;
642 				}
643 
644 			/* if it is a match */
645 			if (matchflag) {
646 
647 				/* if this is the first match, simply record it */
648 				if (match == NULL) {
649 					match = bp;
650 					name[*cpos] = bp->b_bname[*cpos];
651 				} else {
652 					/* if there's a difference, stop here */
653 					if (name[*cpos] != bp->b_bname[*cpos])
654 						return;
655 				}
656 			}
657 
658 			/* on to the next buffer */
659 			bp = bp->b_bufp;
660 		}
661 
662 		/* with no match, we are done */
663 		if (match == NULL) {
664 			/* beep if we never matched */
665 			if (comflag == FALSE)
666 				TTbeep();
667 			return;
668 		}
669 
670 		/* if we have completed all the way... go back */
671 		if (name[*cpos] == 0) {
672 			(*cpos)++;
673 			return;
674 		}
675 
676 		/* remember we matched, and complete one character */
677 		comflag = TRUE;
678 		TTputc(name[(*cpos)++]);
679 		TTflush();
680 	}
681 
682 	/* don't allow a completion past the end of the max buffer name length */
683 	return;
684 }
685 
686 /*	clist_buffer:	Make a completion list based on a partial buffer name */
687 
clist_buffer(name,cpos)688 VOID PASCAL NEAR clist_buffer(name, cpos)
689 
690 char *name;	/* command containing the current name to complete */
691 int *cpos;	/* ptr to position of next character to insert */
692 
693 {
694 	register int name_len;	/* current length of input string */
695 	register BUFFER *listbuf;/* buffer to put completion list into */
696 	register BUFFER *bp;	/* trial buffer to complete */
697 
698 	/* get a buffer for the completion list */
699 	listbuf = bfind("[Completion list]", TRUE, BFINVS);
700 	if (listbuf == NULL || bclear(listbuf) == FALSE) {
701 		ctrlg(FALSE, 0);
702 		TTflush();
703 		return;
704 	}
705 
706 	/* first, we start at the first buffer and scan the list */
707 	name_len = *cpos;
708 	bp = bheadp;
709 
710 	while (bp) {
711 
712 		/* is this a match? */
713 		if (strncmp(name, bp->b_bname, name_len) == 0)
714 			addline(listbuf, bp->b_bname);
715 
716 		/* on to the next buffer */
717 		bp = bp->b_bufp;
718 	}
719 
720 	wpopup(listbuf);
721 	return;
722 }
723 
724 #if	!WINDOW_MSWIN
725 /*	comp_file:	Attempt a completion on a file name	*/
726 
comp_file(name,cpos)727 VOID PASCAL NEAR comp_file(name, cpos)
728 
729 char *name;	/* file containing the current name to complete */
730 int *cpos;	/* ptr to position of next character to insert */
731 
732 {
733 	register char *fname;	/* trial file to complete */
734 	register int index;	/* index into strings to compare */
735 
736 	register int matches;	/* number of matches for name */
737 	char longestmatch[NSTRING]; /* temp buffer for longest match */
738 	int longestlen; 	/* length of longest match (always > *cpos) */
739 
740 	/* everything (or nothing) matches an empty string */
741 	if (*cpos == 0)
742 		return;
743 
744 	/* first, we start at the first file and scan the list */
745 	matches = 0;
746 	name[*cpos] = 0;
747 	fname = getffile(name);
748 	while (fname) {
749 
750 		/* is this a match? */
751 		if (strncmp(name,fname,*cpos) == 0) {
752 
753 			/* count the number of matches */
754 			matches++;
755 
756 			/* if this is the first match, simply record it */
757 			if (matches == 1) {
758 				strcpy(longestmatch,fname);
759 				longestlen = strlen(longestmatch);
760 			} else {
761 
762 				/* if there's a difference, stop here */
763 				if (longestmatch[*cpos] != fname[*cpos])
764 					return;
765 
766 				for (index = (*cpos) + 1; index < longestlen; index++)
767 					if (longestmatch[index] != fname[index]) {
768 						longestlen = index;
769 						longestmatch[longestlen] = 0;
770 					}
771 			}
772 		}
773 
774 		/* on to the next file */
775 		fname = getnfile();
776 	}
777 
778 	/* beep if we never matched */
779 	if (matches == 0) {
780 		TTbeep();
781 		return;
782 	}
783 
784 	/* the longestmatch array contains the longest match so copy and print it */
785 	for ( ; (*cpos < (NSTRING-1)) && (*cpos < longestlen); (*cpos)++) {
786 		name[*cpos] = longestmatch[*cpos];
787 		TTputc(name[*cpos]);
788 	}
789 
790 	name[*cpos] = 0;
791 
792 	/* if only one file matched then increment cpos to signal complete() */
793 	/* that this was a complete match.  If a directory was matched then */
794 	/* last character will be the DIRSEPCHAR.  In this case we do NOT */
795 	/* want to signal a complete match. */
796 	if ((matches == 1) && (name[(*cpos)-1] != DIRSEPCHAR))
797 		(*cpos)++;
798 
799 	TTflush();
800 
801 	return;
802 }
803 
804 /*	clist_file:	Make a completion list based on a partial file name */
805 
clist_file(name,cpos)806 VOID PASCAL NEAR clist_file(name, cpos)
807 
808 char *name;	/* command containing the current name to complete */
809 int *cpos;	/* ptr to position of next character to insert */
810 
811 {
812 	register int name_len;	/* current length of input string */
813 	register BUFFER *listbuf;/* buffer to put completion list into */
814 	register char *fname;	/* trial file to complete */
815 
816 	/* get a buffer for the completion list */
817 	listbuf = bfind("[Completion list]", TRUE, BFINVS);
818 	if (listbuf == NULL || bclear(listbuf) == FALSE) {
819 		ctrlg(FALSE, 0);
820 		TTflush();
821 		return;
822 	}
823 
824 	/* first, we start at the first file and scan the list */
825 	name_len = *cpos;
826 	name[*cpos] = 0;
827 	fname = getffile(name);
828 
829 	/* first, we start at the first file and scan the list */
830 	while (fname) {
831 
832 		/* is this a match? */
833 		if (strncmp(name, fname, name_len) == 0)
834 			addline(listbuf, fname);
835 
836 		/* on to the next file */
837 		fname = getnfile();
838 	}
839 
840 	wpopup(listbuf);
841 	return;
842 }
843 #endif
844 
845 /*	tgetc:	Get a key from the terminal driver, resolve any keyboard
846 		macro action					*/
847 
tgetc()848 int PASCAL NEAR tgetc()
849 
850 {
851 	int c;	/* fetched character */
852 
853 	/* if we are playing a keyboard macro back, */
854 	if (kbdmode == PLAY) {
855 
856 		/* if there is some left... */
857 		if (kbdptr < kbdend)
858 			return((int)*kbdptr++);
859 
860 		/* at the end of last repitition? */
861 		if (--kbdrep < 1) {
862 			kbdmode = STOP;
863 #if	VISMAC == 0
864 			/* force a screen update after all is done */
865 			update(FALSE);
866 #endif
867 		} else {
868 
869 			/* reset the macro to the begining for the next rep */
870 			kbdptr = &kbdm[0];
871 			return((int)*kbdptr++);
872 		}
873 	}
874 
875 	/* if no pending character */
876 	if (cpending == FALSE) {
877 
878 		/* fetch a character from the terminal driver */
879 		c = TTgetc();
880 
881 	} else {
882 
883 		c = charpending;
884 		cpending = FALSE;
885 	}
886 
887 	/* record it for $lastkey */
888 	lastkey = c;
889 
890 	/* save it if we need to */
891 	if (kbdmode == RECORD) {
892 		*kbdptr++ = c;
893 		kbdend = kbdptr;
894 
895 		/* don't overrun the buffer */
896 		if (kbdptr == &kbdm[NKBDM - 1]) {
897 			kbdmode = STOP;
898 			TTbeep();
899 		}
900 	}
901 
902 	/* and finally give the char back */
903 	return(c);
904 }
905 
906 /*	get_key:	Get one keystroke. The legal prefixs here
907 			are the SPEC, MOUS and CTRL prefixes.
908 */
909 
get_key()910 int PASCAL NEAR get_key()
911 
912 {
913 	int c;		/* next input character */
914 	int upper;	/* upper byte of the extended sequence */
915 
916 	/* get a keystroke */
917         c = tgetc();
918 
919 	/* if it exists, process an escape sequence */
920 	if (c == 0) {
921 
922 		/* get the event type */
923 		upper = tgetc();
924 
925 		/* mouse events need us to read in the row/col */
926 		if (upper & (MOUS >> 8)) {
927 			/* grab the x/y position of the mouse */
928 			xpos = tgetc();
929 			ypos = tgetc();
930 		}
931 
932 		/* get the event code */
933 		c = tgetc();
934 
935 		/* if it is a function key... map it */
936 		c = (upper << 8) | c;
937 	}
938 
939 	/* yank out the control prefix */
940         if (((c & 255) >=0x00 && (c & 255) <= 0x1F) || (c & 255) == 0x7F)
941                 c = CTRL | (c ^ 0x40);
942 
943 	/* return the character */
944         return(c);
945 }
946 
947 /*	GETCMD:	Get a command from the keyboard. Process all applicable
948 		prefix keys
949 							*/
getcmd()950 int PASCAL NEAR getcmd()
951 
952 {
953 	int c;		/* fetched keystroke */
954 	KEYTAB *key;	/* ptr to a key entry */
955 
956 	/* get initial character */
957 	c = get_key();
958 	key = getbind(c);
959 
960 	/* resolve META and CTLX prefixes */
961 	if (key) {
962 		if (key->k_ptr.fp == meta) {
963 			c = get_key();
964 #if	SMOS
965 			c = upperc(c&255) | (c & ~255); /* Force to upper */
966 #else
967 			c = upperc(c) | (c & ~255);	/* Force to upper */
968 #endif
969 			c |= META;
970 		} else if (key->k_ptr.fp == cex) {
971 			c = get_key();
972 #if	SMOS
973 			c = upperc(c&255) | (c & ~255); /* Force to upper */
974 #else
975 			c = upperc(c) | (c & ~255);	/* Force to upper */
976 #endif
977 			c |= CTLX;
978 		}
979 	}
980 
981 	/* return it */
982 	return(c);
983 }
984 
985 /*	A more generalized prompt/reply function allowing the caller
986 	to specify the proper terminator. If the terminator is not
987 	a return('\r'), return will echo as "<NL>"
988 							*/
getstring(buf,nbuf,eolchar)989 int PASCAL NEAR getstring(buf, nbuf, eolchar)
990 
991 unsigned char *buf;
992 int nbuf;
993 int eolchar;
994 
995 {
996 	register int cpos;	/* current character position in string */
997 	register int c;		/* current input character */
998 	register int ec;	/* extended current input character */
999 	register int quotef;	/* are we quoting the next char? */
1000 	char *kp;		/* pointer into key_name */
1001 	char key_name[10];	/* name of a quoted key */
1002 
1003 	cpos = 0;
1004 	quotef = FALSE;
1005 
1006 	for (;;) {
1007 		/* get a character from the user */
1008 		ec = get_key();
1009 
1010 		/* if they hit the line terminate, wrap it up */
1011 		if (ec == eolchar && quotef == FALSE) {
1012 			buf[cpos++] = 0;
1013 
1014 			/* clear the message line */
1015 			mlerase();
1016 
1017 			/* if we default the buffer, return FALSE */
1018 			if (buf[0] == 0)
1019 				return(FALSE);
1020 
1021 			return(TRUE);
1022 		}
1023 
1024 		/* change from command form back to character form */
1025 		c = ectoc(ec);
1026 
1027 		if ((ec == abortc) && (quotef == FALSE)) {
1028 			/* Abort the input? */
1029 			ctrlg(FALSE, 0);
1030 			TTflush();
1031 			return(ABORT);
1032 		}
1033 
1034 		/* if it is from the mouse, or is a function key, blow it off */
1035 		if ((quotef == FALSE) && ((ec & MOUS) || (ec & SPEC)))
1036 			continue;
1037 
1038 		/* rubout/erase */
1039 		if ((c==0x7F || c==0x08) && quotef==FALSE) {
1040 			if (cpos != 0) {
1041 				outstring("\b \b");
1042 				--ttcol;
1043 
1044 				if (buf[--cpos] < 0x20) {
1045 					outstring("\b \b");
1046 					--ttcol;
1047 				}
1048 
1049 				if (buf[cpos] == '\r') {
1050 					outstring("\b\b  \b\b");
1051 					ttcol -= 2;
1052 				}
1053 				TTflush();
1054 			}
1055 			continue;
1056  		}
1057 
1058 		/* C-K, kill default buffer and return null */
1059 		if (c == 0x0b && quotef == FALSE) {
1060 
1061  			/* clear the buffer */
1062  			buf[0] = 0;
1063 
1064  			/* clear the message line and return */
1065  			mlwrite("");
1066  			TTflush();
1067  			return(TRUE);
1068 		}
1069 
1070 		/* C-U, kill */
1071 		if (c == 0x15 && quotef == FALSE) {
1072 
1073 			while (cpos != 0) {
1074 				outstring("\b \b");
1075 				--ttcol;
1076 
1077 				if (buf[--cpos] < 0x20) {
1078 					outstring("\b \b");
1079 					--ttcol;
1080 				}
1081 				if (buf[cpos] == '\r') {
1082 					outstring("\b\b  \b\b");
1083 					ttcol -= 2;
1084 				}
1085 			}
1086 			TTflush();
1087 			continue;
1088 		}
1089 
1090 		/* quoting next character? */
1091 		if ((ec == quotec) && (quotef == FALSE)) {
1092 			quotef = TRUE;
1093 			continue;
1094 		}
1095 
1096 		quotef = FALSE;
1097 
1098 		/* if it is from the mouse, or is a function key,
1099 		   insert it's name since it was quoted */
1100 		if ((ec & MOUS) || (ec & SPEC)) {
1101 			cmdstr(ec, key_name);
1102 			kp = key_name;
1103 			while (*kp) {
1104 				if (cpos < nbuf - 1) {
1105 					if (disinp)
1106 						mlout(*kp);
1107 					buf[cpos++] = *kp++;
1108 					++ttcol;
1109 				}
1110 			}
1111 			TTflush();
1112 			continue;
1113 		}
1114 
1115 		/* insert the character in the string! */
1116 		if (cpos < nbuf-1) {
1117 
1118 			buf[cpos++] = c;
1119 
1120 			if ((c < ' ') && (c != '\r')) {
1121 				outstring("^");
1122 				++ttcol;
1123 				c ^= 0x40;
1124 			}
1125 
1126 			if (c != '\r') {
1127 				if (disinp)
1128 					mlout(c);
1129 			} else {	/* put out <NL> for <ret> */
1130 				outstring("<NL>");
1131 				ttcol += 3;
1132 			}
1133 			++ttcol;
1134 			TTflush();
1135 		}
1136 	}
1137 }
1138 
outstring(s)1139 PASCAL NEAR outstring(s) /* output a string of input characters */
1140 
1141 char *s;	/* string to output */
1142 
1143 {
1144 	if (disinp)
1145 		while (*s)
1146 			mlout(*s++);
1147 }
1148 
ostring(s)1149 PASCAL NEAR ostring(s)	/* output a string of output characters */
1150 
1151 char *s;	/* string to output */
1152 
1153 {
1154 	if (discmd)
1155 		while (*s)
1156 			mlout(*s++);
1157 }
1158 
1159 /*
1160  * mlprompt -- Display a prompt [with optional default] and the
1161  *	input terminator.
1162  */
mlprompt(prompt,dflt,iterm)1163 int PASCAL NEAR mlprompt(prompt, dflt, iterm)
1164 char *prompt;
1165 char *dflt;
1166 int iterm;
1167 {
1168 	register int tcol;
1169 	char buf[NSTRING];
1170 
1171 	/* don't bother displaying if we are't currently */
1172 	if (discmd == FALSE)
1173 		return(0);
1174 
1175 	/* show the passed in prompt */
1176 	mlwrite(prompt);
1177 	tcol = strlen(prompt);
1178 
1179 	/* If there's a default, put it in brackets and show it. */
1180 	if (dflt != NULL && *dflt != '\0') {
1181 		mlout('[');
1182 		tcol = 1 + echostring(dflt, tcol + 1, NPAT/2);
1183 		mlout(']');
1184 	}
1185 
1186 	/* Display the proper current search terminator character. */
1187 	mlout('<');
1188 	switch (iterm) {
1189 		case CTRL | '[':
1190 			mlputs("META"); tcol += 8; break;
1191 		case CTRL | 'M':
1192 			mlputs("NL"); tcol += 6; break;
1193 		default:
1194 			mlputs(cmdstr(iterm, buf));
1195 			tcol += strlen(buf) + 4;
1196 	}
1197 	mlputs(">: ");
1198 	movecursor(term.t_nrow, tcol);	/* Position the cursor	*/
1199 	TTflush();
1200 	return(tcol);
1201 }
1202 
1203 /*
1204  * echostring -- Use echochar() to put out a string.  Checks for NULL.
1205  */
echostring(str,tcol,uptocol)1206 int PASCAL NEAR echostring(str, tcol, uptocol)
1207 char *str;	/* characters to be echoed */
1208 int tcol;	/* column to be echoed in */
1209 int uptocol;	/* last column to be echoed in */
1210 {
1211 	if (str != NULL) {
1212 		while (*str) {
1213 			movecursor(term.t_nrow, tcol);	/* Position the cursor	*/
1214 			tcol += echochar(*str++);
1215 			if (tcol >= uptocol) {
1216 				mlout('$');
1217 				tcol++;
1218 				break;
1219 			}
1220 		}
1221 	}
1222 	movecursor(term.t_nrow, tcol);	/* Position the cursor	*/
1223 	return(tcol);
1224 }
1225 
1226 /*
1227  * Routine to echo i-search and message-prompting characters.
1228  */
echochar(c)1229 int PASCAL NEAR echochar(c)
1230 
1231 unsigned char c;	/* character to be echoed */
1232 
1233 {
1234 	int col = 0;			/* column to be echoed in */
1235 
1236 	if (c == '\r') {		/* Newline character?	*/
1237 		mlout('<');
1238 		mlout('N');
1239 		mlout('L');
1240 		mlout('>');
1241 		col = 3;
1242 	}
1243 #if 0
1244 	else if (c == '\t') {		/* Tab character?	*/
1245 		mlout('<');
1246 		mlout('T');
1247 		mlout('A');
1248 		mlout('B');
1249 		mlout('>');
1250 		col = 4;
1251 	}
1252 #endif
1253 	else if ((c < ' ') || (c == 0x7F)) {	/* Vanilla control char and Rubout:   */
1254 		mlout('^'); 	/* Yes, output prefix		*/
1255 		mlout(c ^ 0x40);/* Make it "^X"			*/
1256 		col++;		/* Count this char		*/
1257 	}
1258 	else
1259 		mlout(c);		/* Otherwise, output raw char	*/
1260 	TTflush();			/* Flush the output		*/
1261 	return(++col);			/* return the new column number */
1262 }
1263