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