sh.file.c revision 145479
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
36RCSID("$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
58typedef enum {
59    LIST, RECOGNIZE
60}       COMMAND;
61
62static	void	 setup_tty		__P((int));
63static	void	 back_to_col_1		__P((void));
64static	void	 pushback		__P((Char *));
65static	void	 catn			__P((Char *, Char *, int));
66static	void	 copyn			__P((Char *, Char *, int));
67static	int	 filetype		__P((Char *, Char *));
68static	void	 print_by_column	__P((Char *, Char *[], size_t));
69static	Char 	*tilde			__P((Char *, Char *));
70static	void	 retype			__P((void));
71static	void	 beep			__P((void));
72static	void 	 print_recognized_stuff	__P((Char *));
73static	void	 extract_dir_and_name	__P((Char *, Char *, Char *));
74static	Char	*getitem		__P((DIR *, int));
75static	void	 free_items		__P((Char **, size_t));
76static	int	 tsearch		__P((Char *, COMMAND, int));
77static	int	 compare		__P((const ptr_t, const ptr_t));
78static	int	 recognize		__P((Char *, Char *, int, size_t));
79static	int	 is_prefix		__P((Char *, Char *));
80static	int	 is_suffix		__P((Char *, Char *));
81static	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 */
89int    filec = 0;
90
91static void
92setup_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 */
166static void
167back_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 */
224static void
225pushback(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 */
295static void
296catn(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 */
312static void
313copyn(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
323static int
324filetype(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
354static struct winsize win;
355
356/*
357 * Print sorted down columns
358 */
359static void
360print_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 */
402static Char *
403tilde(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 */
430static void
431retype()
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
458static void
459beep()
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 */
476static void
477print_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 */
509static void
510extract_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
526static Char *
527getitem(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
548static void
549free_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 */
579static int
580tsearch(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
611again:				/* 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
685static int
686compare(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 */
719static int
720recognize(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 */
745static int
746is_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 */
760static int
761is_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
776int
777tenex(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
847static int
848ignored(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