sh.file.c revision 100616
1/* $Header: /src/pub/tcsh/sh.file.c,v 3.22 2002/07/01 20:53:00 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.22 2002/07/01 20:53:00 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	Char	 filetype		__P((Char *, Char *));
68static	void	 print_by_column	__P((Char *, Char *[], int));
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 **));
76static	int	 tsearch		__P((Char *, COMMAND, int));
77static	int	 compare		__P((const ptr_t, const ptr_t));
78static	int	 recognize		__P((Char *, Char *, int, int));
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 */
89bool    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    char    c;
230#ifdef TERMIO
231# ifdef POSIX
232    struct termios tty, tty_normal;
233# else
234    struct termio tty, tty_normal;
235# endif /* POSIX */
236#else
237    struct sgttyb tty, tty_normal;
238#endif /* TERMIO */
239
240#ifdef BSDSIGS
241    sigmask_t omask = sigblock(sigmask(SIGINT));
242#else
243    (void) sighold(SIGINT);
244#endif /* BSDSIGS */
245
246#ifdef TERMIO
247# ifdef POSIX
248    (void) tcgetattr(SHOUT, &tty);
249# else
250    (void) ioctl(SHOUT, TCSETAW, (ioctl_t) &tty);
251# endif /* POSIX */
252    tty_normal = tty;
253    tty.c_lflag &= ~(ECHOKE | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOCTL);
254# ifdef POSIX
255    (void) tcsetattr(SHOUT, TCSANOW, &tty);
256# else
257    (void) ioctl(SHOUT, TCSETAW, (ioctl_t) &tty);
258# endif /* POSIX */
259
260    for (p = string; (c = *p) != '\0'; p++)
261	(void) ioctl(SHOUT, TIOCSTI, (ioctl_t) & c);
262# ifdef POSIX
263    (void) tcsetattr(SHOUT, TCSANOW, &tty_normal);
264# else
265    (void) ioctl(SHOUT, TCSETAW, (ioctl_t) &tty_normal);
266# endif /* POSIX */
267#else
268    (void) ioctl(SHOUT, TIOCGETP, (ioctl_t) & tty);
269    tty_normal = tty;
270    tty.sg_flags &= ~ECHO;
271    (void) ioctl(SHOUT, TIOCSETN, (ioctl_t) & tty);
272
273    for (p = string; c = *p; p++)
274	(void) ioctl(SHOUT, TIOCSTI, (ioctl_t) & c);
275    (void) ioctl(SHOUT, TIOCSETN, (ioctl_t) & tty_normal);
276#endif /* TERMIO */
277
278# ifdef BSDSIGS
279    (void) sigsetmask(omask);
280# else
281    (void) sigrelse(SIGINT);
282# endif /* BSDISGS */
283}
284
285/*
286 * Concatenate src onto tail of des.
287 * Des is a string whose maximum length is count.
288 * Always null terminate.
289 */
290static void
291catn(des, src, count)
292    Char *des, *src;
293    int count;
294{
295    while (--count >= 0 && *des)
296	des++;
297    while (--count >= 0)
298	if ((*des++ = *src++) == 0)
299	    return;
300    *des = '\0';
301}
302
303/*
304 * Like strncpy but always leave room for trailing \0
305 * and always null terminate.
306 */
307static void
308copyn(des, src, count)
309    Char *des, *src;
310    int count;
311{
312    while (--count >= 0)
313	if ((*des++ = *src++) == 0)
314	    return;
315    *des = '\0';
316}
317
318static  Char
319filetype(dir, file)
320    Char   *dir, *file;
321{
322    Char    path[MAXPATHLEN];
323    struct stat statb;
324
325    catn(Strcpy(path, dir), file, sizeof(path) / sizeof(Char));
326    if (lstat(short2str(path), &statb) == 0) {
327	switch (statb.st_mode & S_IFMT) {
328	case S_IFDIR:
329	    return ('/');
330
331	case S_IFLNK:
332	    if (stat(short2str(path), &statb) == 0 &&	/* follow it out */
333		S_ISDIR(statb.st_mode))
334		return ('>');
335	    else
336		return ('@');
337
338	case S_IFSOCK:
339	    return ('=');
340
341	default:
342	    if (statb.st_mode & 0111)
343		return ('*');
344	}
345    }
346    return (' ');
347}
348
349static struct winsize win;
350
351/*
352 * Print sorted down columns
353 */
354static void
355print_by_column(dir, items, count)
356    Char   *dir, *items[];
357    int     count;
358{
359    int i, rows, r, c, maxwidth = 0, columns;
360
361    if (ioctl(SHOUT, TIOCGWINSZ, (ioctl_t) & win) < 0 || win.ws_col == 0)
362	win.ws_col = 80;
363    for (i = 0; i < count; i++)
364	maxwidth = maxwidth > (r = Strlen(items[i])) ? maxwidth : r;
365    maxwidth += 2;		/* for the file tag and space */
366    columns = win.ws_col / maxwidth;
367    if (columns == 0)
368	columns = 1;
369    rows = (count + (columns - 1)) / columns;
370    for (r = 0; r < rows; r++) {
371	for (c = 0; c < columns; c++) {
372	    i = c * rows + r;
373	    if (i < count) {
374		int w;
375
376		xprintf("%S", items[i]);
377		xputchar(dir ? filetype(dir, items[i]) : ' ');
378		if (c < columns - 1) {	/* last column? */
379		    w = Strlen(items[i]) + 1;
380		    for (; w < maxwidth; w++)
381			xputchar(' ');
382		}
383	    }
384	}
385	xputchar('\r');
386	xputchar('\n');
387    }
388}
389
390/*
391 * Expand file name with possible tilde usage
392 *	~person/mumble
393 * expands to
394 *	home_directory_of_person/mumble
395 */
396static Char *
397tilde(new, old)
398    Char   *new, *old;
399{
400    Char *o, *p;
401    struct passwd *pw;
402    static Char person[40];
403
404    if (old[0] != '~')
405	return (Strcpy(new, old));
406
407    for (p = person, o = &old[1]; *o && *o != '/'; *p++ = *o++);
408    *p = '\0';
409    if (person[0] == '\0')
410	(void) Strcpy(new, varval(STRhome));
411    else {
412	pw = getpwnam(short2str(person));
413	if (pw == NULL)
414	    return (NULL);
415	(void) Strcpy(new, str2short(pw->pw_dir));
416    }
417    (void) Strcat(new, o);
418    return (new);
419}
420
421/*
422 * Cause pending line to be printed
423 */
424static void
425retype()
426{
427#ifdef TERMIO
428# ifdef POSIX
429    struct termios tty;
430
431    (void) tcgetattr(SHOUT, &tty);
432# else
433    struct termio tty;
434
435    (void) ioctl(SHOUT, TCGETA, (ioctl_t) &tty);
436# endif /* POSIX */
437
438    tty.c_lflag |= PENDIN;
439
440# ifdef POSIX
441    (void) tcsetattr(SHOUT, TCSANOW, &tty);
442# else
443    (void) ioctl(SHOUT, TCSETAW, (ioctl_t) &tty);
444# endif /* POSIX */
445#else
446    int     pending_input = LPENDIN;
447
448    (void) ioctl(SHOUT, TIOCLBIS, (ioctl_t) & pending_input);
449#endif /* TERMIO */
450}
451
452static void
453beep()
454{
455    if (adrof(STRnobeep) == 0)
456#ifdef IS_ASCII
457	(void) write(SHOUT, "\007", 1);
458#else
459    {
460	unsigned char beep_ch = CTL_ESC('\007');
461	(void) write(SHOUT, &beep_ch, 1);
462    }
463#endif
464}
465
466/*
467 * Erase that silly ^[ and
468 * print the recognized part of the string
469 */
470static void
471print_recognized_stuff(recognized_part)
472    Char   *recognized_part;
473{
474    /* An optimized erasing of that silly ^[ */
475    (void) putraw('\b');
476    (void) putraw('\b');
477    switch (Strlen(recognized_part)) {
478
479    case 0:			/* erase two Characters: ^[ */
480	(void) putraw(' ');
481	(void) putraw(' ');
482	(void) putraw('\b');
483	(void) putraw('\b');
484	break;
485
486    case 1:			/* overstrike the ^, erase the [ */
487	xprintf("%S", recognized_part);
488	(void) putraw(' ');
489	(void) putraw('\b');
490	break;
491
492    default:			/* overstrike both Characters ^[ */
493	xprintf("%S", recognized_part);
494	break;
495    }
496    flush();
497}
498
499/*
500 * Parse full path in file into 2 parts: directory and file names
501 * Should leave final slash (/) at end of dir.
502 */
503static void
504extract_dir_and_name(path, dir, name)
505    Char   *path, *dir, *name;
506{
507    Char *p;
508
509    p = Strrchr(path, '/');
510    if (p == NULL) {
511	copyn(name, path, MAXNAMLEN);
512	dir[0] = '\0';
513    }
514    else {
515	copyn(name, ++p, MAXNAMLEN);
516	copyn(dir, path, p - path);
517    }
518}
519/* atp vmsposix - I need to remove all the setpwent
520 *		  getpwent endpwent stuff. VMS_POSIX has getpwnam getpwuid
521 *		  and getlogin. This needs fixing. (There is no access to
522 *		  pw->passwd in VMS - a secure system benefit :-| )
523 */
524static Char *
525getitem(dir_fd, looking_for_lognames)
526    DIR    *dir_fd;
527    int     looking_for_lognames;
528{
529    struct passwd *pw;
530    struct dirent *dirp;
531
532    if (looking_for_lognames) {
533#ifdef _VMS_POSIX
534	    return (NULL);
535#else
536	if ((pw = getpwent()) == NULL)
537	    return (NULL);
538	return (str2short(pw->pw_name));
539#endif /* atp vmsposix */
540    }
541    if ((dirp = readdir(dir_fd)) != NULL)
542	return (str2short(dirp->d_name));
543    return (NULL);
544}
545
546static void
547free_items(items)
548    Char **items;
549{
550    int i;
551
552    for (i = 0; items[i]; i++)
553	xfree((ptr_t) items[i]);
554    xfree((ptr_t) items);
555}
556
557#ifdef BSDSIGS
558# define FREE_ITEMS(items) { \
559	sigmask_t omask;\
560\
561	omask = sigblock(sigmask(SIGINT));\
562	free_items(items);\
563	items = NULL;\
564	(void) sigsetmask(omask);\
565}
566#else
567# define FREE_ITEMS(items) { \
568	(void) sighold(SIGINT);\
569	free_items(items);\
570	items = NULL;\
571	(void) sigrelse(SIGINT);\
572}
573#endif /* BSDSIGS */
574
575/*
576 * Perform a RECOGNIZE or LIST command on string "word".
577 */
578static int
579tsearch(word, command, max_word_length)
580    Char   *word;
581    int     max_word_length;
582    COMMAND command;
583{
584    static Char **items = NULL;
585    DIR *dir_fd;
586    int numitems = 0, 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
592#define MAXITEMS 1024
593
594    if (items != NULL)
595	FREE_ITEMS(items);
596
597    looking_for_lognames = (*word == '~') && (Strchr(word, '/') == NULL);
598    if (looking_for_lognames) {
599#ifndef _VMS_POSIX
600	(void) setpwent();
601#endif /*atp vmsposix */
602	copyn(name, &word[1], MAXNAMLEN);	/* name sans ~ */
603	dir_fd = NULL;
604    }
605    else {
606	extract_dir_and_name(word, dir, name);
607	if (tilde(tilded_dir, dir) == 0)
608	    return (0);
609	dir_fd = opendir(*tilded_dir ? short2str(tilded_dir) : ".");
610	if (dir_fd == NULL)
611	    return (0);
612    }
613
614again:				/* search for matches */
615    name_length = Strlen(name);
616    for (numitems = 0;
617	(item = getitem(dir_fd, looking_for_lognames)) != NULL;) {
618	if (!is_prefix(name, item))
619	    continue;
620	/* Don't match . files on null prefix match */
621	if (name_length == 0 && item[0] == '.' &&
622	    !looking_for_lognames)
623	    continue;
624	if (command == LIST) {
625	    if (numitems >= MAXITEMS) {
626		xprintf(CGETS(14, 1, "\nYikes!! Too many %s!!\n"),
627			looking_for_lognames ?
628			CGETS(14, 2, "names in password file") :
629			CGETS(14, 3, "files"));
630		break;
631	    }
632	    /*
633	     * From Beto Appleton (beto@aixwiz.austin.ibm.com)
634	     *	typing "./control-d" will cause the csh to core-dump.
635	     *	the problem can be reproduce as following:
636	     *	 1. set ignoreeof
637	     *	 2. set filec
638	     *	 3. create a directory with 1050 files
639	     *	 4. typing "./control-d" will cause the csh to core-dump
640	     * Solution: Add + 1 to MAXITEMS
641	     */
642	    if (items == NULL)
643		items = (Char **) xcalloc(sizeof(items[0]), MAXITEMS + 1);
644	    items[numitems] = (Char *) xmalloc((size_t) (Strlen(item) + 1) *
645					       sizeof(Char));
646	    copyn(items[numitems], item, MAXNAMLEN);
647	    numitems++;
648	}
649	else {			/* RECOGNIZE command */
650	    if (ignoring && ignored(item))
651		nignored++;
652	    else if (recognize(extended_name,
653			       item, name_length, ++numitems))
654		break;
655	}
656    }
657    if (ignoring && numitems == 0 && nignored > 0) {
658	ignoring = FALSE;
659	nignored = 0;
660	if (looking_for_lognames)
661#ifndef _VMS_POSIX
662	    (void) setpwent();
663#endif /* atp vmsposix */
664	else
665	    rewinddir(dir_fd);
666	goto again;
667    }
668
669    if (looking_for_lognames)
670#ifndef _VMS_POSIX
671	(void) endpwent();
672#endif /*atp vmsposix */
673    else
674	(void) closedir(dir_fd);
675    if (numitems == 0)
676	return (0);
677    if (command == RECOGNIZE) {
678	if (looking_for_lognames)
679	    copyn(word, STRtilde, 1);
680	else
681	    /* put back dir part */
682	    copyn(word, dir, max_word_length);
683	/* add extended name */
684	catn(word, extended_name, max_word_length);
685	return (numitems);
686    }
687    else {			/* LIST */
688	qsort((ptr_t) items, (size_t) numitems, sizeof(items[0]),
689	    (int (*) __P((const void *, const void *))) compare);
690	print_by_column(looking_for_lognames ? NULL : tilded_dir,
691			items, numitems);
692	if (items != NULL)
693	    FREE_ITEMS(items);
694    }
695    return (0);
696}
697
698
699static int
700compare(p, q)
701    const ptr_t  p, q;
702{
703#if defined(NLS) && !defined(NOSTRCOLL)
704    errno = 0;  /* strcoll sets errno, another brain-damage */
705
706    return (strcoll(*(char **) p, *(char **) q));
707#else
708    return (strcmp(*(char **) p, *(char **) q));
709#endif /* NLS && !NOSTRCOLL */
710}
711
712/*
713 * Object: extend what user typed up to an ambiguity.
714 * Algorithm:
715 * On first match, copy full item (assume it'll be the only match)
716 * On subsequent matches, shorten extended_name to the first
717 * Character mismatch between extended_name and item.
718 * If we shorten it back to the prefix length, stop searching.
719 */
720static int
721recognize(extended_name, item, name_length, numitems)
722    Char   *extended_name, *item;
723    int     name_length, 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];
783
784
785    setup_tty(ON);
786
787    while ((num_read = read(SHIN, tinputline, BUFSIZE)) > 0) {
788	int     i;
789	static Char delims[] = {' ', '\'', '"', '\t', ';', '&', '<',
790	'>', '(', ')', '|', '^', '%', '\0'};
791	Char *str_end, *word_start, last_Char, should_retype;
792	int space_left;
793	COMMAND command;
794
795	for (i = 0; i < num_read; i++)
796	    inputline[i] = (unsigned char) tinputline[i];
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