xref: /freebsd/contrib/tcsh/sh.file.c (revision 87569f75a91f298c52a71823c04d41cf53c88889)
1 /* $Header: /src/pub/tcsh/sh.file.c,v 3.28 2005/01/05 16:06:13 christos Exp $ */
2 /*
3  * sh.file.c: File completion for csh. This file is not used in tcsh.
4  */
5 /*-
6  * Copyright (c) 1980, 1991 The Regents of the University of California.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 #include "sh.h"
34 #include "ed.h"
35 
36 RCSID("$Id: sh.file.c,v 3.28 2005/01/05 16:06:13 christos Exp $")
37 
38 #if defined(FILEC) && defined(TIOCSTI)
39 
40 /*
41  * Tenex style file name recognition, .. and more.
42  * History:
43  *	Author: Ken Greer, Sept. 1975, CMU.
44  *	Finally got around to adding to the Cshell., Ken Greer, Dec. 1981.
45  */
46 
47 #define ON	1
48 #define OFF	0
49 #ifndef TRUE
50 #define TRUE 1
51 #endif
52 #ifndef FALSE
53 #define FALSE 0
54 #endif
55 
56 #define ESC     CTL_ESC('\033')
57 
58 typedef enum {
59     LIST, RECOGNIZE
60 }       COMMAND;
61 
62 static	void	 setup_tty		__P((int));
63 static	void	 back_to_col_1		__P((void));
64 static	void	 pushback		__P((Char *));
65 static	void	 catn			__P((Char *, Char *, int));
66 static	void	 copyn			__P((Char *, Char *, int));
67 static	int	 filetype		__P((Char *, Char *));
68 static	void	 print_by_column	__P((Char *, Char *[], size_t));
69 static	Char 	*tilde			__P((Char *, Char *));
70 static	void	 retype			__P((void));
71 static	void	 beep			__P((void));
72 static	void 	 print_recognized_stuff	__P((Char *));
73 static	void	 extract_dir_and_name	__P((Char *, Char *, Char *));
74 static	Char	*getitem		__P((DIR *, int));
75 static	void	 free_items		__P((Char **, size_t));
76 static	int	 tsearch		__P((Char *, COMMAND, int));
77 static	int	 compare		__P((const ptr_t, const ptr_t));
78 static	int	 recognize		__P((Char *, Char *, int, size_t));
79 static	int	 is_prefix		__P((Char *, Char *));
80 static	int	 is_suffix		__P((Char *, Char *));
81 static	int	 ignored		__P((Char *));
82 
83 
84 /*
85  * Put this here so the binary can be patched with adb to enable file
86  * completion by default.  Filec controls completion, nobeep controls
87  * ringing the terminal bell on incomplete expansions.
88  */
89 int    filec = 0;
90 
91 static void
92 setup_tty(on)
93     int     on;
94 {
95 #ifdef TERMIO
96 # ifdef POSIX
97     struct termios tchars;
98 # else
99     struct termio tchars;
100 # endif /* POSIX */
101 
102 # ifdef POSIX
103     (void) tcgetattr(SHIN, &tchars);
104 # else
105     (void) ioctl(SHIN, TCGETA, (ioctl_t) &tchars);
106 # endif /* POSIX */
107     if (on) {
108 	tchars.c_cc[VEOL] = ESC;
109 	if (tchars.c_lflag & ICANON)
110 # ifdef POSIX
111 	    on = TCSADRAIN;
112 # else
113 	    on = TCSETA;
114 # endif /* POSIX */
115 	else {
116 # ifdef POSIX
117 	    on = TCSAFLUSH;
118 # else
119 	    on = TCSETAF;
120 # endif /* POSIX */
121 	    tchars.c_lflag |= ICANON;
122 
123 	}
124     }
125     else {
126 	tchars.c_cc[VEOL] = _POSIX_VDISABLE;
127 # ifdef POSIX
128 	on = TCSADRAIN;
129 # else
130         on = TCSETA;
131 # endif /* POSIX */
132     }
133 # ifdef POSIX
134     (void) tcsetattr(SHIN, on, &tchars);
135 # else
136     (void) ioctl(SHIN, on, (ioctl_t) &tchars);
137 # endif /* POSIX */
138 #else
139     struct sgttyb sgtty;
140     static struct tchars tchars;/* INT, QUIT, XON, XOFF, EOF, BRK */
141 
142     if (on) {
143 	(void) ioctl(SHIN, TIOCGETC, (ioctl_t) & tchars);
144 	tchars.t_brkc = ESC;
145 	(void) ioctl(SHIN, TIOCSETC, (ioctl_t) & tchars);
146 	/*
147 	 * This must be done after every command: if the tty gets into raw or
148 	 * cbreak mode the user can't even type 'reset'.
149 	 */
150 	(void) ioctl(SHIN, TIOCGETP, (ioctl_t) & sgtty);
151 	if (sgtty.sg_flags & (RAW | CBREAK)) {
152 	    sgtty.sg_flags &= ~(RAW | CBREAK);
153 	    (void) ioctl(SHIN, TIOCSETP, (ioctl_t) & sgtty);
154 	}
155     }
156     else {
157 	tchars.t_brkc = -1;
158 	(void) ioctl(SHIN, TIOCSETC, (ioctl_t) & tchars);
159     }
160 #endif /* TERMIO */
161 }
162 
163 /*
164  * Move back to beginning of current line
165  */
166 static void
167 back_to_col_1()
168 {
169 #ifdef TERMIO
170 # ifdef POSIX
171     struct termios tty, tty_normal;
172 # else
173     struct termio tty, tty_normal;
174 # endif /* POSIX */
175 #else
176     struct sgttyb tty, tty_normal;
177 #endif /* TERMIO */
178 
179 # ifdef BSDSIGS
180     sigmask_t omask = sigblock(sigmask(SIGINT));
181 # else
182     (void) sighold(SIGINT);
183 # endif /* BSDSIGS */
184 
185 #ifdef TERMIO
186 # ifdef POSIX
187     (void) tcgetattr(SHOUT, &tty);
188 # else
189     (void) ioctl(SHOUT, TCGETA, (ioctl_t) &tty_normal);
190 # endif /* POSIX */
191     tty_normal = tty;
192     tty.c_iflag &= ~INLCR;
193     tty.c_oflag &= ~ONLCR;
194 # ifdef POSIX
195     (void) tcsetattr(SHOUT, TCSANOW, &tty);
196 # else
197     (void) ioctl(SHOUT, TCSETAW, (ioctl_t) &tty);
198 # endif /* POSIX */
199     (void) write(SHOUT, "\r", 1);
200 # ifdef POSIX
201     (void) tcsetattr(SHOUT, TCSANOW, &tty_normal);
202 # else
203     (void) ioctl(SHOUT, TCSETAW, (ioctl_t) &tty_normal);
204 # endif /* POSIX */
205 #else
206     (void) ioctl(SHIN, TIOCGETP, (ioctl_t) & tty);
207     tty_normal = tty;
208     tty.sg_flags &= ~CRMOD;
209     (void) ioctl(SHIN, TIOCSETN, (ioctl_t) & tty);
210     (void) write(SHOUT, "\r", 1);
211     (void) ioctl(SHIN, TIOCSETN, (ioctl_t) & tty_normal);
212 #endif /* TERMIO */
213 
214 # ifdef BSDSIGS
215     (void) sigsetmask(omask);
216 # else
217     (void) sigrelse(SIGINT);
218 # endif /* BSDISGS */
219 }
220 
221 /*
222  * Push string contents back into tty queue
223  */
224 static void
225 pushback(string)
226     Char   *string;
227 {
228     Char *p;
229 #ifdef TERMIO
230 # ifdef POSIX
231     struct termios tty, tty_normal;
232 # else
233     struct termio tty, tty_normal;
234 # endif /* POSIX */
235 #else
236     struct sgttyb tty, tty_normal;
237 #endif /* TERMIO */
238 
239 #ifdef BSDSIGS
240     sigmask_t omask = sigblock(sigmask(SIGINT));
241 #else
242     (void) sighold(SIGINT);
243 #endif /* BSDSIGS */
244 
245 #ifdef TERMIO
246 # ifdef POSIX
247     (void) tcgetattr(SHOUT, &tty);
248 # else
249     (void) ioctl(SHOUT, TCSETAW, (ioctl_t) &tty);
250 # endif /* POSIX */
251     tty_normal = tty;
252     tty.c_lflag &= ~(ECHOKE | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOCTL);
253 # ifdef POSIX
254     (void) tcsetattr(SHOUT, TCSANOW, &tty);
255 # else
256     (void) ioctl(SHOUT, TCSETAW, (ioctl_t) &tty);
257 # endif /* POSIX */
258 
259     for (p = string; *p != '\0'; p++) {
260 	char buf[MB_LEN_MAX];
261 	size_t i, len;
262 
263 	len = one_wctomb(buf, *p & CHAR);
264 	for (i = 0; i < len; i++)
265 	    (void) ioctl(SHOUT, TIOCSTI, (ioctl_t) &buf[i]);
266     }
267 # ifdef POSIX
268     (void) tcsetattr(SHOUT, TCSANOW, &tty_normal);
269 # else
270     (void) ioctl(SHOUT, TCSETAW, (ioctl_t) &tty_normal);
271 # endif /* POSIX */
272 #else
273     (void) ioctl(SHOUT, TIOCGETP, (ioctl_t) & tty);
274     tty_normal = tty;
275     tty.sg_flags &= ~ECHO;
276     (void) ioctl(SHOUT, TIOCSETN, (ioctl_t) & tty);
277 
278     for (p = string; c = *p; p++)
279 	(void) ioctl(SHOUT, TIOCSTI, (ioctl_t) & c);
280     (void) ioctl(SHOUT, TIOCSETN, (ioctl_t) & tty_normal);
281 #endif /* TERMIO */
282 
283 # ifdef BSDSIGS
284     (void) sigsetmask(omask);
285 # else
286     (void) sigrelse(SIGINT);
287 # endif /* BSDISGS */
288 }
289 
290 /*
291  * Concatenate src onto tail of des.
292  * Des is a string whose maximum length is count.
293  * Always null terminate.
294  */
295 static void
296 catn(des, src, count)
297     Char *des, *src;
298     int count;
299 {
300     while (--count >= 0 && *des)
301 	des++;
302     while (--count >= 0)
303 	if ((*des++ = *src++) == 0)
304 	    return;
305     *des = '\0';
306 }
307 
308 /*
309  * Like strncpy but always leave room for trailing \0
310  * and always null terminate.
311  */
312 static void
313 copyn(des, src, count)
314     Char *des, *src;
315     int count;
316 {
317     while (--count >= 0)
318 	if ((*des++ = *src++) == 0)
319 	    return;
320     *des = '\0';
321 }
322 
323 static int
324 filetype(dir, file)
325     Char   *dir, *file;
326 {
327     Char    path[MAXPATHLEN];
328     struct stat statb;
329 
330     catn(Strcpy(path, dir), file, sizeof(path) / sizeof(Char));
331     if (lstat(short2str(path), &statb) == 0) {
332 	switch (statb.st_mode & S_IFMT) {
333 	case S_IFDIR:
334 	    return ('/');
335 
336 	case S_IFLNK:
337 	    if (stat(short2str(path), &statb) == 0 &&	/* follow it out */
338 		S_ISDIR(statb.st_mode))
339 		return ('>');
340 	    else
341 		return ('@');
342 
343 	case S_IFSOCK:
344 	    return ('=');
345 
346 	default:
347 	    if (statb.st_mode & 0111)
348 		return ('*');
349 	}
350     }
351     return (' ');
352 }
353 
354 static struct winsize win;
355 
356 /*
357  * Print sorted down columns
358  */
359 static void
360 print_by_column(dir, items, count)
361     Char   *dir, *items[];
362     size_t  count;
363 {
364     size_t i;
365     int rows, r, c, maxwidth = 0, columns;
366 
367     if (ioctl(SHOUT, TIOCGWINSZ, (ioctl_t) & win) < 0 || win.ws_col == 0)
368 	win.ws_col = 80;
369     for (i = 0; i < count; i++)
370 	maxwidth = maxwidth > (r = Strlen(items[i])) ? maxwidth : r;
371     maxwidth += 2;		/* for the file tag and space */
372     columns = win.ws_col / maxwidth;
373     if (columns == 0)
374 	columns = 1;
375     rows = (count + (columns - 1)) / columns;
376     for (r = 0; r < rows; r++) {
377 	for (c = 0; c < columns; c++) {
378 	    i = c * rows + r;
379 	    if (i < count) {
380 		int w;
381 
382 		xprintf("%S", items[i]);
383 		xputchar(dir ? filetype(dir, items[i]) : ' ');
384 		if (c < columns - 1) {	/* last column? */
385 		    w = Strlen(items[i]) + 1;
386 		    for (; w < maxwidth; w++)
387 			xputchar(' ');
388 		}
389 	    }
390 	}
391 	xputchar('\r');
392 	xputchar('\n');
393     }
394 }
395 
396 /*
397  * Expand file name with possible tilde usage
398  *	~person/mumble
399  * expands to
400  *	home_directory_of_person/mumble
401  */
402 static Char *
403 tilde(new, old)
404     Char   *new, *old;
405 {
406     Char *o, *p;
407     struct passwd *pw;
408     static Char person[40];
409 
410     if (old[0] != '~')
411 	return (Strcpy(new, old));
412 
413     for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++);
414     *p = '\0';
415     if (person[0] == '\0')
416 	(void) Strcpy(new, varval(STRhome));
417     else {
418 	pw = getpwnam(short2str(person));
419 	if (pw == NULL)
420 	    return (NULL);
421 	(void) Strcpy(new, str2short(pw->pw_dir));
422     }
423     (void) Strcat(new, o);
424     return (new);
425 }
426 
427 /*
428  * Cause pending line to be printed
429  */
430 static void
431 retype()
432 {
433 #ifdef TERMIO
434 # ifdef POSIX
435     struct termios tty;
436 
437     (void) tcgetattr(SHOUT, &tty);
438 # else
439     struct termio tty;
440 
441     (void) ioctl(SHOUT, TCGETA, (ioctl_t) &tty);
442 # endif /* POSIX */
443 
444     tty.c_lflag |= PENDIN;
445 
446 # ifdef POSIX
447     (void) tcsetattr(SHOUT, TCSANOW, &tty);
448 # else
449     (void) ioctl(SHOUT, TCSETAW, (ioctl_t) &tty);
450 # endif /* POSIX */
451 #else
452     int     pending_input = LPENDIN;
453 
454     (void) ioctl(SHOUT, TIOCLBIS, (ioctl_t) & pending_input);
455 #endif /* TERMIO */
456 }
457 
458 static void
459 beep()
460 {
461     if (adrof(STRnobeep) == 0)
462 #ifdef IS_ASCII
463 	(void) write(SHOUT, "\007", 1);
464 #else
465     {
466 	unsigned char beep_ch = CTL_ESC('\007');
467 	(void) write(SHOUT, &beep_ch, 1);
468     }
469 #endif
470 }
471 
472 /*
473  * Erase that silly ^[ and
474  * print the recognized part of the string
475  */
476 static void
477 print_recognized_stuff(recognized_part)
478     Char   *recognized_part;
479 {
480     /* An optimized erasing of that silly ^[ */
481     (void) putraw('\b');
482     (void) putraw('\b');
483     switch (Strlen(recognized_part)) {
484 
485     case 0:			/* erase two Characters: ^[ */
486 	(void) putraw(' ');
487 	(void) putraw(' ');
488 	(void) putraw('\b');
489 	(void) putraw('\b');
490 	break;
491 
492     case 1:			/* overstrike the ^, erase the [ */
493 	xprintf("%S", recognized_part);
494 	(void) putraw(' ');
495 	(void) putraw('\b');
496 	break;
497 
498     default:			/* overstrike both Characters ^[ */
499 	xprintf("%S", recognized_part);
500 	break;
501     }
502     flush();
503 }
504 
505 /*
506  * Parse full path in file into 2 parts: directory and file names
507  * Should leave final slash (/) at end of dir.
508  */
509 static void
510 extract_dir_and_name(path, dir, name)
511     Char   *path, *dir, *name;
512 {
513     Char *p;
514 
515     p = Strrchr(path, '/');
516     if (p == NULL) {
517 	copyn(name, path, MAXNAMLEN);
518 	dir[0] = '\0';
519     }
520     else {
521 	copyn(name, ++p, MAXNAMLEN);
522 	copyn(dir, path, p - path);
523     }
524 }
525 
526 static Char *
527 getitem(dir_fd, looking_for_lognames)
528     DIR    *dir_fd;
529     int     looking_for_lognames;
530 {
531     struct passwd *pw;
532     struct dirent *dirp;
533 
534     if (looking_for_lognames) {
535 #ifndef HAVE_GETPWENT
536 	    return (NULL);
537 #else
538 	if ((pw = getpwent()) == NULL)
539 	    return (NULL);
540 	return (str2short(pw->pw_name));
541 #endif /* atp vmsposix */
542     }
543     if ((dirp = readdir(dir_fd)) != NULL)
544 	return (str2short(dirp->d_name));
545     return (NULL);
546 }
547 
548 static void
549 free_items(items, numitems)
550     Char **items;
551     size_t numitems;
552 {
553     size_t i;
554 
555     for (i = 0; i < numitems; i++)
556 	xfree((ptr_t) items[i]);
557     xfree((ptr_t) items);
558 }
559 
560 #ifdef BSDSIGS
561 # define FREE_ITEMS(items, numitems) { \
562 	sigmask_t omask;\
563 \
564 	omask = sigblock(sigmask(SIGINT));\
565 	free_items(items, numitems);\
566 	(void) sigsetmask(omask);\
567 }
568 #else
569 # define FREE_ITEMS(items, numitems) { \
570 	(void) sighold(SIGINT);\
571 	free_items(items, numitems);\
572 	(void) sigrelse(SIGINT);\
573 }
574 #endif /* BSDSIGS */
575 
576 /*
577  * Perform a RECOGNIZE or LIST command on string "word".
578  */
579 static int
580 tsearch(word, command, max_word_length)
581     Char   *word;
582     int     max_word_length;
583     COMMAND command;
584 {
585     DIR *dir_fd;
586     int ignoring = TRUE, nignored = 0;
587     int name_length, looking_for_lognames;
588     Char    tilded_dir[MAXPATHLEN + 1], dir[MAXPATHLEN + 1];
589     Char    name[MAXNAMLEN + 1], extended_name[MAXNAMLEN + 1];
590     Char   *item;
591     Char **items = NULL;
592     size_t numitems = 0, maxitems = 0;
593 
594     looking_for_lognames = (*word == '~') && (Strchr(word, '/') == NULL);
595     if (looking_for_lognames) {
596 #ifdef HAVE_GETPWENT
597 	(void) setpwent();
598 #endif
599 	copyn(name, &word[1], MAXNAMLEN);	/* name sans ~ */
600 	dir_fd = NULL;
601     }
602     else {
603 	extract_dir_and_name(word, dir, name);
604 	if (tilde(tilded_dir, dir) == 0)
605 	    return (0);
606 	dir_fd = opendir(*tilded_dir ? short2str(tilded_dir) : ".");
607 	if (dir_fd == NULL)
608 	    return (0);
609     }
610 
611 again:				/* search for matches */
612     name_length = Strlen(name);
613     for (numitems = 0;
614 	(item = getitem(dir_fd, looking_for_lognames)) != NULL;) {
615 	if (!is_prefix(name, item))
616 	    continue;
617 	/* Don't match . files on null prefix match */
618 	if (name_length == 0 && item[0] == '.' &&
619 	    !looking_for_lognames)
620 	    continue;
621 	if (command == LIST) {
622 	    if (numitems >= maxitems) {
623 		maxitems += 1024;
624 		if (items == NULL)
625 			items = (Char **) xmalloc(sizeof(*items) * maxitems);
626 		else
627 			items = (Char **) xrealloc((ptr_t) items,
628 			    sizeof(*items) * maxitems);
629 	    }
630 	    items[numitems] = (Char *) xmalloc((size_t) (Strlen(item) + 1) *
631 					       sizeof(Char));
632 	    copyn(items[numitems], item, MAXNAMLEN);
633 	    numitems++;
634 	}
635 	else {			/* RECOGNIZE command */
636 	    if (ignoring && ignored(item))
637 		nignored++;
638 	    else if (recognize(extended_name,
639 			       item, name_length, ++numitems))
640 		break;
641 	}
642     }
643     if (ignoring && numitems == 0 && nignored > 0) {
644 	ignoring = FALSE;
645 	nignored = 0;
646 	if (looking_for_lognames) {
647 #ifdef HAVE_GETPWENT
648 	    (void) setpwent();
649 #endif /* atp vmsposix */
650 	} else
651 	    rewinddir(dir_fd);
652 	goto again;
653     }
654 
655     if (looking_for_lognames) {
656 #ifndef HAVE_GETPWENT
657 	(void) endpwent();
658 #endif
659     } else
660 	(void) closedir(dir_fd);
661     if (numitems == 0)
662 	return (0);
663     if (command == RECOGNIZE) {
664 	if (looking_for_lognames)
665 	    copyn(word, STRtilde, 1);
666 	else
667 	    /* put back dir part */
668 	    copyn(word, dir, max_word_length);
669 	/* add extended name */
670 	catn(word, extended_name, max_word_length);
671 	return (numitems);
672     }
673     else {			/* LIST */
674 	qsort((ptr_t) items, numitems, sizeof(items[0]),
675 	    (int (*) __P((const void *, const void *))) compare);
676 	print_by_column(looking_for_lognames ? NULL : tilded_dir,
677 			items, numitems);
678 	if (items != NULL)
679 	    FREE_ITEMS(items, numitems);
680     }
681     return (0);
682 }
683 
684 
685 static int
686 compare(p, q)
687     const ptr_t  p, q;
688 {
689 #ifdef WIDE_STRINGS
690     errno = 0;
691 
692     return (wcscoll(*(Char **) p, *(Char **) q));
693 #else
694     char *p1, *q1;
695     int res;
696 
697     p1 = strsave(short2str(*(Char **) p));
698     q1 = strsave(short2str(*(Char **) q));
699 # if defined(NLS) && !defined(NOSTRCOLL)
700     errno = 0;  /* strcoll sets errno, another brain-damage */
701     res = strcoll(p1, q1);
702 # else
703     res = strcmp(p1, q1);
704 # endif /* NLS && !NOSTRCOLL */
705     xfree (p1);
706     xfree (q1);
707     return res;
708 #endif /* not WIDE_STRINGS */
709 }
710 
711 /*
712  * Object: extend what user typed up to an ambiguity.
713  * Algorithm:
714  * On first match, copy full item (assume it'll be the only match)
715  * On subsequent matches, shorten extended_name to the first
716  * Character mismatch between extended_name and item.
717  * If we shorten it back to the prefix length, stop searching.
718  */
719 static int
720 recognize(extended_name, item, name_length, numitems)
721     Char   *extended_name, *item;
722     int     name_length;
723     size_t  numitems;
724 {
725     if (numitems == 1)		/* 1st match */
726 	copyn(extended_name, item, MAXNAMLEN);
727     else {			/* 2nd & subsequent matches */
728 	Char *x, *ent;
729 	int len = 0;
730 
731 	x = extended_name;
732 	for (ent = item; *x && *x == *ent++; x++, len++);
733 	*x = '\0';		/* Shorten at 1st Char diff */
734 	if (len == name_length)	/* Ambiguous to prefix? */
735 	    return (-1);	/* So stop now and save time */
736     }
737     return (0);
738 }
739 
740 /*
741  * Return true if check matches initial Chars in template.
742  * This differs from PWB imatch in that if check is null
743  * it matches anything.
744  */
745 static int
746 is_prefix(check, template)
747     Char *check, *template;
748 {
749     do
750 	if (*check == 0)
751 	    return (TRUE);
752     while (*check++ == *template++);
753     return (FALSE);
754 }
755 
756 /*
757  *  Return true if the Chars in template appear at the
758  *  end of check, I.e., are it's suffix.
759  */
760 static int
761 is_suffix(check, template)
762     Char   *check, *template;
763 {
764     Char *c, *t;
765 
766     for (c = check; *c++;);
767     for (t = template; *t++;);
768     for (;;) {
769 	if (t == template)
770 	    return 1;
771 	if (c == check || *--t != *--c)
772 	    return 0;
773     }
774 }
775 
776 int
777 tenex(inputline, inputline_size)
778     Char   *inputline;
779     int     inputline_size;
780 {
781     int numitems, num_read;
782     char    tinputline[BUFSIZE + 1];
783 
784 
785     setup_tty(ON);
786 
787     while ((num_read = read(SHIN, tinputline, BUFSIZE)) > 0) {
788 	static Char delims[] = {' ', '\'', '"', '\t', ';', '&', '<',
789 	'>', '(', ')', '|', '^', '%', '\0'};
790 	Char *str_end, *word_start, last_Char, should_retype;
791 	int space_left;
792 	COMMAND command;
793 
794 	tinputline[num_read] = 0;
795 	Strcpy(inputline, str2short(tinputline));
796 	num_read = Strlen(inputline);
797 	last_Char = inputline[num_read - 1] & ASCII;
798 
799 	if (last_Char == '\n' || num_read == inputline_size)
800 	    break;
801 	command = (last_Char == ESC) ? RECOGNIZE : LIST;
802 	if (command == LIST)
803 	    xputchar('\n');
804 	str_end = &inputline[num_read];
805 	if (last_Char == ESC)
806 	    --str_end;		/* wipeout trailing cmd Char */
807 	*str_end = '\0';
808 	/*
809 	 * Find LAST occurence of a delimiter in the inputline. The word start
810 	 * is one Character past it.
811 	 */
812 	for (word_start = str_end; word_start > inputline; --word_start)
813 	    if (Strchr(delims, word_start[-1]))
814 		break;
815 	space_left = inputline_size - (word_start - inputline) - 1;
816 	numitems = tsearch(word_start, command, space_left);
817 
818 	if (command == RECOGNIZE) {
819 	    /* print from str_end on */
820 	    print_recognized_stuff(str_end);
821 	    if (numitems != 1)	/* Beep = No match/ambiguous */
822 		beep();
823 	}
824 
825 	/*
826 	 * Tabs in the input line cause trouble after a pushback. tty driver
827 	 * won't backspace over them because column positions are now
828 	 * incorrect. This is solved by retyping over current line.
829 	 */
830 	should_retype = FALSE;
831 	if (Strchr(inputline, '\t')) {	/* tab Char in input line? */
832 	    back_to_col_1();
833 	    should_retype = TRUE;
834 	}
835 	if (command == LIST)	/* Always retype after a LIST */
836 	    should_retype = TRUE;
837 	if (should_retype)
838 	    printprompt(0, NULL);
839 	pushback(inputline);
840 	if (should_retype)
841 	    retype();
842     }
843     setup_tty(OFF);
844     return (num_read);
845 }
846 
847 static int
848 ignored(item)
849     Char *item;
850 {
851     struct varent *vp;
852     Char **cp;
853 
854     if ((vp = adrof(STRfignore)) == NULL || (cp = vp->vec) == NULL)
855 	return (FALSE);
856     for (; *cp != NULL; cp++)
857 	if (is_suffix(item, *cp))
858 	    return (TRUE);
859     return (FALSE);
860 }
861 #endif	/* FILEC && TIOCSTI */
862