xref: /netbsd-src/bin/ksh/edit.c (revision 037708cbd4616ccd0d7d0381ebd3964d6696c188)
1 /*	$NetBSD: edit.c,v 1.2 1997/01/12 19:11:44 tls Exp $	*/
2 
3 /*
4  * Command line editing - common code
5  *
6  */
7 
8 #include "config.h"
9 #ifdef EDIT
10 
11 #include "sh.h"
12 #include "tty.h"
13 #define EXTERN
14 #include "edit.h"
15 #undef EXTERN
16 #ifdef OS_SCO	/* SCO Unix 3.2v4.1 */
17 # include <sys/stream.h>	/* needed for <sys/ptem.h> */
18 # include <sys/ptem.h>		/* needed for struct winsize */
19 #endif /* OS_SCO */
20 #include <ctype.h>
21 #include "ksh_stat.h"
22 
23 
24 #if defined(TIOCGWINSZ)
25 static RETSIGTYPE x_sigwinch ARGS((int sig));
26 static int got_sigwinch;
27 static void check_sigwinch ARGS((void));
28 #endif /* TIOCGWINSZ */
29 
30 static int	x_file_glob ARGS((int flags, const char *str, int slen,
31 				  char ***wordsp));
32 static int	x_command_glob ARGS((int flags, const char *str, int slen,
33 				     char ***wordsp));
34 static int	x_locate_word ARGS((const char *buf, int buflen, int pos,
35 				    int *startp, int *is_command));
36 
37 static char vdisable_c;
38 
39 
40 /* Called from main */
41 void
42 x_init()
43 {
44 	/* set to -1 to force initial binding */
45 	edchars.erase = edchars.kill = edchars.intr = edchars.quit
46 		= edchars.eof = -1;
47 	/* default value for deficient systems */
48 	edchars.werase = 027;	/* ^W */
49 
50 #ifdef TIOCGWINSZ
51 # ifdef SIGWINCH
52 	if (setsig(&sigtraps[SIGWINCH], x_sigwinch, SS_RESTORE_ORIG|SS_SHTRAP))
53 		sigtraps[SIGWINCH].flags |= TF_SHELL_USES;
54 # endif /* SIGWINCH */
55 	got_sigwinch = 1; /* force initial check */
56 	check_sigwinch();
57 #endif /* TIOCGWINSZ */
58 
59 #ifdef EMACS
60 	x_init_emacs();
61 #endif /* EMACS */
62 
63 	/* Bizarreness to figure out how to disable
64 	 * a struct termios.c_cc[] char
65 	 */
66 #ifdef _POSIX_VDISABLE
67 	if (_POSIX_VDISABLE >= 0)
68 		vdisable_c = (char) _POSIX_VDISABLE;
69 	else
70 		/* `feature not available' */
71 		vdisable_c = (char) 0377;
72 #else
73 # if defined(HAVE_PATHCONF) && defined(_PC_VDISABLE)
74 	vdisable_c = fpathconf(tty_fd, _PC_VDISABLE);
75 # else
76 	vdisable_c = (char) 0377;	/* default to old BSD value */
77 # endif
78 #endif /* _POSIX_VDISABLE */
79 }
80 
81 #if defined(TIOCGWINSZ)
82 static RETSIGTYPE
83 x_sigwinch(sig)
84     	int sig;
85 {
86 	got_sigwinch = 1;
87 	return RETSIGVAL;
88 }
89 
90 static void
91 check_sigwinch ARGS((void))
92 {
93 	if (got_sigwinch) {
94 		struct winsize ws;
95 
96 		got_sigwinch = 0;
97 		if (procpid == kshpid && ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) {
98 			struct tbl *vp;
99 
100 			/* Do NOT export COLUMNS/LINES.  Many applications
101 			 * check COLUMNS/LINES before checking ws.ws_col/row,
102 			 * so if the app is started with C/L in the environ
103 			 * and the window is then resized, the app won't
104 			 * see the change cause the environ doesn't change.
105 			 */
106 			if (ws.ws_col) {
107 				x_cols = ws.ws_col < MIN_COLS ? MIN_COLS
108 						: ws.ws_col;
109 
110 				if ((vp = typeset("COLUMNS", 0, 0, 0, 0)))
111 					setint(vp, (long) ws.ws_col);
112 			}
113 			if (ws.ws_row
114 			    && (vp = typeset("LINES", 0, 0, 0, 0)))
115 				setint(vp, (long) ws.ws_row);
116 		}
117 	}
118 }
119 #endif /* TIOCGWINSZ */
120 
121 /*
122  * read an edited command line
123  */
124 int
125 x_read(buf, len)
126 	char *buf;
127 	size_t len;
128 {
129 	int	i;
130 
131 #if defined(TIOCGWINSZ)
132 	if (got_sigwinch)
133 		check_sigwinch();
134 #endif /* TIOCGWINSZ */
135 
136 	x_mode(TRUE);
137 #ifdef EMACS
138 	if (Flag(FEMACS) || Flag(FGMACS))
139 		i = x_emacs(buf, len);
140 	else
141 #endif
142 #ifdef VI
143 	if (Flag(FVI))
144 		i = x_vi(buf, len);
145 	else
146 #endif
147 		i = -1;		/* internal error */
148 	x_mode(FALSE);
149 	return i;
150 }
151 
152 /* tty I/O */
153 
154 int
155 x_getc()
156 {
157 #ifdef OS2
158 	unsigned char c = _read_kbd(0, 1, 0);
159 	return c == 0 ? 0xE0 : c;
160 #else /* OS2 */
161 	char c;
162 	int n;
163 
164 	while ((n = blocking_read(0, &c, 1)) < 0 && errno == EINTR)
165 		if (trap) {
166 			x_mode(FALSE);
167 			runtraps(0);
168 			x_mode(TRUE);
169 		}
170 	if (n != 1)
171 		return -1;
172 	return (int) (unsigned char) c;
173 #endif /* OS2 */
174 }
175 
176 void
177 x_flush()
178 {
179 	shf_flush(shl_out);
180 }
181 
182 void
183 x_putc(c)
184 	int c;
185 {
186 	shf_putc(c, shl_out);
187 }
188 
189 void
190 x_puts(s)
191 	const char *s;
192 {
193 	while (*s != 0)
194 		shf_putc(*s++, shl_out);
195 }
196 
197 bool_t
198 x_mode(onoff)
199 	bool_t	onoff;
200 {
201 	static bool_t	x_cur_mode;
202 	bool_t		prev;
203 
204 	if (x_cur_mode == onoff)
205 		return x_cur_mode;
206 	prev = x_cur_mode;
207 	x_cur_mode = onoff;
208 
209 	if (onoff) {
210 		TTY_state	cb;
211 		X_chars		oldchars;
212 
213 		oldchars = edchars;
214 		cb = tty_state;
215 
216 #if defined(HAVE_TERMIOS_H) || defined(HAVE_TERMIO_H)
217 		edchars.erase = cb.c_cc[VERASE];
218 		edchars.kill = cb.c_cc[VKILL];
219 		edchars.intr = cb.c_cc[VINTR];
220 		edchars.quit = cb.c_cc[VQUIT];
221 		edchars.eof = cb.c_cc[VEOF];
222 # ifdef VWERASE
223 		edchars.werase = cb.c_cc[VWERASE];
224 # endif
225 # ifdef _CRAY2		/* brain-damaged terminal handler */
226 		cb.c_lflag &= ~(ICANON|ECHO);
227 		/* rely on print routine to map '\n' to CR,LF */
228 # else
229 		cb.c_iflag &= ~(INLCR|ICRNL);
230 #  ifdef _BSD_SYSV	/* need to force CBREAK instead of RAW (need CRMOD on output) */
231 		cb.c_lflag &= ~(ICANON|ECHO);
232 #  else
233 #   ifdef SWTCH	/* need CBREAK to handle swtch char */
234 		cb.c_lflag &= ~(ICANON|ECHO);
235 		cb.c_lflag |= ISIG;
236 		cb.c_cc[VINTR] = vdisable_c;
237 		cb.c_cc[VQUIT] = vdisable_c;
238 #   else
239 		cb.c_lflag &= ~(ISIG|ICANON|ECHO);
240 #   endif
241 #  endif
242 #  ifdef VLNEXT
243 		/* osf/1 processes lnext when ~icanon */
244 		cb.c_cc[VLNEXT] = vdisable_c;
245 #  endif /* VLNEXT */
246 #  ifdef VDISCARD
247 		/* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */
248 		cb.c_cc[VDISCARD] = vdisable_c;
249 #  endif /* VDISCARD */
250 		cb.c_cc[VTIME] = 0;
251 		cb.c_cc[VMIN] = 1;
252 # endif	/* _CRAY2 */
253 #else
254 	/* Assume BSD tty stuff. */
255 		edchars.erase = cb.sgttyb.sg_erase;
256 		edchars.kill = cb.sgttyb.sg_kill;
257 		cb.sgttyb.sg_flags &= ~ECHO;
258 		cb.sgttyb.sg_flags |= CBREAK;
259 #  ifdef TIOCGATC
260 		edchars.intr = cb.lchars.tc_intrc;
261 		edchars.quit = cb.lchars.tc_quitc;
262 		edchars.eof = cb.lchars.tc_eofc;
263 		edchars.werase = cb.lchars.tc_werasc;
264 		cb.lchars.tc_suspc = -1;
265 		cb.lchars.tc_dsuspc = -1;
266 		cb.lchars.tc_lnextc = -1;
267 		cb.lchars.tc_statc = -1;
268 		cb.lchars.tc_intrc = -1;
269 		cb.lchars.tc_quitc = -1;
270 		cb.lchars.tc_rprntc = -1;
271 #  else
272 		edchars.intr = cb.tchars.t_intrc;
273 		edchars.quit = cb.tchars.t_quitc;
274 		edchars.eof = cb.tchars.t_eofc;
275 		cb.tchars.t_intrc = -1;
276 		cb.tchars.t_quitc = -1;
277 #   ifdef TIOCGLTC
278 		edchars.werase = cb.ltchars.t_werasc;
279 		cb.ltchars.t_suspc = -1;
280 		cb.ltchars.t_dsuspc = -1;
281 		cb.ltchars.t_lnextc = -1;
282 		cb.ltchars.t_rprntc = -1;
283 #   endif
284 #  endif /* TIOCGATC */
285 #endif /* HAVE_TERMIOS_H || HAVE_TERMIO_H */
286 
287 		set_tty(tty_fd, &cb, TF_WAIT);
288 
289 		if (memcmp(&edchars, &oldchars, sizeof(edchars)) != 0) {
290 #ifdef EMACS
291 			x_emacs_keys(&edchars);
292 #endif
293 		}
294 	} else
295 		/* TF_WAIT doesn't seem to be necessary when leaving xmode */
296 		set_tty(tty_fd, &tty_state, TF_NONE);
297 
298 	return prev;
299 }
300 
301 /* NAME:
302  *      promptlen - calculate the length of PS1 etc.
303  *
304  * DESCRIPTION:
305  *      This function is based on a fix from guy@demon.co.uk
306  *      It fixes a bug in that if PS1 contains '!', the length
307  *      given by strlen() is probably wrong.
308  *
309  * RETURN VALUE:
310  *      length
311  */
312 int
313 promptlen(cp, spp)
314     const char  *cp;
315     const char **spp;
316 {
317     int count = 0;
318     const char *sp = cp;
319     char delimiter = 0;
320     int indelimit = 0;
321 
322     /* Undocumented AT&T ksh feature:
323      * If the second char in the prompt string is \r then the first char
324      * is taken to be a non-printing delimiter and any chars between two
325      * instances of the delimiter are not considered to be part of the
326      * prompt length
327      */
328     if (*cp && cp[1] == '\r') {
329 	delimiter = *cp;
330 	cp += 2;
331     }
332 
333     for (; *cp; cp++) {
334 	if (indelimit && *cp != delimiter)
335 	    ;
336 	else if (*cp == '\n' || *cp == '\r') {
337 	    count = 0;
338 	    sp = cp + 1;
339 	} else if (*cp == '\t') {
340 	    count = (count | 7) + 1;
341 	} else if (*cp == '\b') {
342 	    if (count > 0)
343 		count--;
344 	} else if (*cp == delimiter)
345 	    indelimit = !indelimit;
346 	else
347 	    count++;
348     }
349     if (spp)
350 	*spp = sp;
351     return count;
352 }
353 
354 void
355 set_editmode(ed)
356 	const char *ed;
357 {
358 	static const enum sh_flag edit_flags[] = {
359 #ifdef EMACS
360 			FEMACS, FGMACS,
361 #endif
362 #ifdef VI
363 			FVI,
364 #endif
365 		    };
366 	char *rcp;
367 	int i;
368 
369 	if ((rcp = ksh_strrchr_dirsep(ed)))
370 		ed = ++rcp;
371 	for (i = 0; i < NELEM(edit_flags); i++)
372 		if (strstr(ed, options[(int) edit_flags[i]].name)) {
373 			change_flag(edit_flags[i], OF_SPECIAL, 1);
374 			return;
375 		}
376 }
377 
378 /* ------------------------------------------------------------------------- */
379 /*           Misc common code for vi/emacs				     */
380 
381 /* Handle the commenting/uncommenting of a line.
382  * Returns:
383  *	1 if a carriage return is indicated (comment added)
384  *	0 if no return (comment removed)
385  *	-1 if there is an error (not enough room for comment chars)
386  * If successful, *lenp contains the new length.  Note: cursor should be
387  * moved to the start of the line after (un)commenting.
388  */
389 int
390 x_do_comment(buf, bsize, lenp)
391 	char *buf;
392 	int bsize;
393 	int *lenp;
394 {
395 	int i, j;
396 	int len = *lenp;
397 
398 	if (len == 0)
399 		return 1; /* somewhat arbitrary - it's what at&t ksh does */
400 
401 	/* Already commented? */
402 	if (buf[0] == '#') {
403 		int saw_nl = 0;
404 
405 		for (j = 0, i = 1; i < len; i++) {
406 			if (!saw_nl || buf[i] != '#')
407 				buf[j++] = buf[i];
408 			saw_nl = buf[i] == '\n';
409 		}
410 		*lenp = j;
411 		return 0;
412 	} else {
413 		int n = 1;
414 
415 		/* See if there's room for the #'s - 1 per \n */
416 		for (i = 0; i < len; i++)
417 			if (buf[i] == '\n')
418 				n++;
419 		if (len + n >= bsize)
420 			return -1;
421 		/* Now add them... */
422 		for (i = len, j = len + n; --i >= 0; ) {
423 			if (buf[i] == '\n')
424 				buf[--j] = '#';
425 			buf[--j] = buf[i];
426 		}
427 		buf[0] = '#';
428 		*lenp += n;
429 		return 1;
430 	}
431 }
432 
433 /* ------------------------------------------------------------------------- */
434 /*           Common file/command completion code for vi/emacs	             */
435 
436 
437 static char	*add_glob ARGS((const char *str, int slen));
438 static void	glob_table ARGS((const char *pat, XPtrV *wp, struct table *tp));
439 static void	glob_path ARGS((int flags, const char *pat, XPtrV *wp,
440 				const char *path));
441 
442 #if 0 /* not used... */
443 int	x_complete_word ARGS((const char *str, int slen, int is_command,
444 			      int *multiple, char **ret));
445 int
446 x_complete_word(str, slen, is_command, nwordsp, ret)
447 	const char *str;
448 	int slen;
449 	int is_command;
450 	int *nwordsp;
451 	char **ret;
452 {
453 	int nwords;
454 	int prefix_len;
455 	char **words;
456 
457 	nwords = (is_command ? x_command_glob : x_file_glob)(XCF_FULLPATH,
458 				str, slen, &words);
459 	*nwordsp = nwords;
460 	if (nwords == 0) {
461 		*ret = (char *) 0;
462 		return -1;
463 	}
464 
465 	prefix_len = x_longest_prefix(nwords, words);
466 	*ret = str_nsave(words[0], prefix_len, ATEMP);
467 	x_free_words(nwords, words);
468 	return prefix_len;
469 }
470 #endif /* 0 */
471 
472 void
473 x_print_expansions(nwords, words, is_command)
474 	int nwords;
475 	char *const *words;
476 	int is_command;
477 {
478 	int use_copy = 0;
479 	int prefix_len;
480 	XPtrV l;
481 
482 	/* Check if all matches are in the same directory (in this
483 	 * case, we want to omitt the directory name)
484 	 */
485 	if (!is_command
486 	    && (prefix_len = x_longest_prefix(nwords, words)) > 0)
487 	{
488 		int i;
489 
490 		/* Special case for 1 match (prefix is whole word) */
491 		if (nwords == 1)
492 			prefix_len = x_basename(words[0], (char *) 0);
493 		/* Any (non-trailing) slashes in non-common word suffixes? */
494 		for (i = 0; i < nwords; i++)
495 			if (x_basename(words[i] + prefix_len, (char *) 0)
496 							> prefix_len)
497 				break;
498 		/* All in same directory? */
499 		if (i == nwords) {
500 			while (prefix_len > 0
501 			       && !ISDIRSEP(words[0][prefix_len - 1]))
502 				prefix_len--;
503 			use_copy = 1;
504 			XPinit(l, nwords + 1);
505 			for (i = 0; i < nwords; i++)
506 				XPput(l, words[i] + prefix_len);
507 			XPput(l, (char *) 0);
508 		}
509 	}
510 
511 	/*
512 	 * Enumerate expansions
513 	 */
514 	x_putc('\r');
515 	x_putc('\n');
516 	pr_menu(use_copy ? (char **) XPptrv(l) : words);
517 
518 	if (use_copy)
519 		XPfree(l); /* not x_free_words() */
520 }
521 
522 /*
523  *  Do file globbing:
524  *	- appends * to (copy of) str if no globbing chars found
525  *	- does expansion, checks for no match, etc.
526  *	- sets *wordsp to array of matching strings
527  *	- returns number of matching strings
528  */
529 static int
530 x_file_glob(flags, str, slen, wordsp)
531 	int flags;
532 	const char *str;
533 	int slen;
534 	char ***wordsp;
535 {
536 	char *toglob;
537 	char **words;
538 	int nwords;
539 	XPtrV w;
540 	struct source *s, *sold;
541 
542 	if (slen < 0)
543 		return 0;
544 
545 	toglob = add_glob(str, slen);
546 
547 	/*
548 	 * Convert "foo*" (toglob) to an array of strings (words)
549 	 */
550 	sold = source;
551 	s = pushs(SWSTR, ATEMP);
552 	s->start = s->str = toglob;
553 	source = s;
554 	if (yylex(ONEWORD) != LWORD) {
555 		source = sold;
556 		internal_errorf(0, "fileglob: substitute error");
557 		return 0;
558 	}
559 	source = sold;
560 	XPinit(w, 32);
561 	expand(yylval.cp, &w, DOGLOB|DOTILDE|DOMARKDIRS);
562 	XPput(w, NULL);
563 	words = (char **) XPclose(w);
564 
565 	for (nwords = 0; words[nwords]; nwords++)
566 		;
567 	if (nwords == 1) {
568 		struct stat statb;
569 
570 		/* Check if globbing failed (returned glob pattern),
571 		 * but be careful (E.g. toglob == "ab*" when the file
572 		 * "ab*" exists is not an error).
573 		 * Also, check for empty result - happens if we tried
574 		 * to glob something which evaluated to an empty
575 		 * string (e.g., "$FOO" when there is no FOO, etc).
576 		 */
577 		if ((strcmp(words[0], toglob) == 0
578 		     && stat(words[0], &statb) < 0)
579 		    || words[0][0] == '\0')
580 		{
581 			x_free_words(nwords, words);
582 			nwords = 0;
583 		}
584 	}
585 	afree(toglob, ATEMP);
586 
587 	*wordsp = nwords ? words : (char **) 0;
588 
589 	return nwords;
590 }
591 
592 /* Data structure used in x_command_glob() */
593 struct path_order_info {
594 	char *word;
595 	int base;
596 	int path_order;
597 };
598 
599 /* Compare routine used in x_command_glob() */
600 static int
601 path_order_cmp(aa, bb)
602 	const void *aa;
603 	const void *bb;
604 {
605 	const struct path_order_info *a = (const struct path_order_info *) aa;
606 	const struct path_order_info *b = (const struct path_order_info *) bb;
607 	int t;
608 
609 	t = FILECMP(a->word + a->base, b->word + b->base);
610 	return t ? t : a->path_order - b->path_order;
611 }
612 
613 static int
614 x_command_glob(flags, str, slen, wordsp)
615 	int flags;
616 	const char *str;
617 	int slen;
618 	char ***wordsp;
619 {
620 	char *toglob;
621 	char *pat;
622 	char *fpath;
623 	int nwords;
624 	XPtrV w;
625 	struct block *l;
626 
627 	if (slen < 0)
628 		return 0;
629 
630 	toglob = add_glob(str, slen);
631 
632 	/* Convert "foo*" (toglob) to a pattern for future use */
633 	pat = evalstr(toglob, DOPAT|DOTILDE);
634 	afree(toglob, ATEMP);
635 
636 	XPinit(w, 32);
637 
638 	glob_table(pat, &w, &keywords);
639 	glob_table(pat, &w, &aliases);
640 	glob_table(pat, &w, &builtins);
641 	for (l = e->loc; l; l = l->next)
642 		glob_table(pat, &w, &l->funs);
643 
644 	glob_path(flags, pat, &w, path);
645 	if ((fpath = str_val(global("FPATH"))) != null)
646 		glob_path(flags, pat, &w, fpath);
647 
648 	nwords = XPsize(w);
649 
650 	if (!nwords) {
651 		*wordsp = (char **) 0;
652 		XPfree(w);
653 		return 0;
654 	}
655 
656 	/* Sort entries */
657 	if (flags & XCF_FULLPATH) {
658 		/* Sort by basename, then path order */
659 		struct path_order_info *info;
660 		struct path_order_info *last_info = 0;
661 		char **words = (char **) XPptrv(w);
662 		int path_order = 0;
663 		int i;
664 
665 		info = (struct path_order_info *)
666 			alloc(sizeof(struct path_order_info) * nwords, ATEMP);
667 		for (i = 0; i < nwords; i++) {
668 			info[i].word = words[i];
669 			info[i].base = x_basename(words[i], (char *) 0);
670 			if (!last_info || info[i].base != last_info->base
671 			    || FILENCMP(words[i],
672 					last_info->word, info[i].base) != 0)
673 			{
674 				last_info = &info[i];
675 				path_order++;
676 			}
677 			info[i].path_order = path_order;
678 		}
679 		qsort(info, nwords, sizeof(struct path_order_info),
680 			path_order_cmp);
681 		for (i = 0; i < nwords; i++)
682 			words[i] = info[i].word;
683 		afree((void *) info, ATEMP);
684 	} else {
685 		/* Sort and remove duplicate entries */
686 		char **words = (char **) XPptrv(w);
687 		int i, j;
688 
689 		qsortp(XPptrv(w), (size_t) nwords, xstrcmp);
690 
691 		for (i = j = 0; i < nwords - 1; i++) {
692 			if (strcmp(words[i], words[i + 1]))
693 				words[j++] = words[i];
694 			else
695 				afree(words[i], ATEMP);
696 		}
697 		words[j++] = words[i];
698 		nwords = j;
699 		w.cur = (void **) &words[j];
700 	}
701 
702 	XPput(w, NULL);
703 	*wordsp = (char **) XPclose(w);
704 
705 	return nwords;
706 }
707 
708 #define IS_WORDC(c)	!isspace(c)
709 
710 static int
711 x_locate_word(buf, buflen, pos, startp, is_commandp)
712 	const char *buf;
713 	int buflen;
714 	int pos;
715 	int *startp;
716 	int *is_commandp;
717 {
718 	int p;
719 	int start, end;
720 
721 	/* Bad call?  Probably should report error */
722 	if (pos < 0 || pos > buflen) {
723 		*startp = pos;
724 		*is_commandp = 0;
725 		return 0;
726 	}
727 
728 	if (pos == buflen) {
729 		if (pos == 0) { /* empty buffer? */
730 			*startp = pos;
731 			*is_commandp = 1;
732 			return 0;
733 		}
734 		pos--;
735 	}
736 
737 	start = pos;
738 	/* Keep going backwards to start of word (has effect of allowing
739 	 * one blank after the end of a word)
740 	 */
741 	for (; start > 0 && IS_WORDC(buf[start - 1]); start--)
742 		;
743 	/* Go forwards to end of word */
744 	for (end = start; end < buflen && IS_WORDC(buf[end]); end++)
745 		;
746 
747 	if (is_commandp) {
748 		int iscmd;
749 
750 		/* Figure out if this is a command */
751 		for (p = start - 1; p >= 0 && isspace(buf[p]); p--)
752 			;
753 		iscmd = p < 0 || strchr(";|&()", buf[p]);
754 		if (iscmd) {
755 			/* If command has a /, path, etc. is not searched;
756 			 * only current directory is searched, which is just
757 			 * like file globbing.
758 			 */
759 			for (p = start; p < end; p++)
760 				if (ISDIRSEP(buf[p]))
761 					break;
762 			iscmd = p == end;
763 		}
764 		*is_commandp = iscmd;
765 	}
766 
767 	*startp = start;
768 
769 	return end - start;
770 }
771 
772 int
773 x_cf_glob(flags, buf, buflen, pos, startp, endp, wordsp, is_commandp)
774 	int flags;
775 	const char *buf;
776 	int buflen;
777 	int pos;
778 	int *startp;
779 	int *endp;
780 	char ***wordsp;
781 	int *is_commandp;
782 {
783 	int len;
784 	int nwords;
785 	char **words;
786 	int is_command;
787 
788 	len = x_locate_word(buf, buflen, pos, startp, &is_command);
789 	if (!(flags & XCF_COMMAND))
790 		is_command = 0;
791 	/* Don't do command globing on zero length strings - it takes too
792 	 * long and isn't very useful.  File globs are more likely to be
793 	 * useful, so allow these.
794 	 */
795 	if (len == 0 && is_command)
796 		return 0;
797 
798 	nwords = (is_command ? x_command_glob : x_file_glob)(flags,
799 				    buf + *startp, len, &words);
800 	if (nwords == 0) {
801 		*wordsp = (char **) 0;
802 		return 0;
803 	}
804 
805 	if (is_commandp)
806 		*is_commandp = is_command;
807 	*wordsp = words;
808 	*endp = *startp + len;
809 
810 	return nwords;
811 }
812 
813 /* Given a string, copy it and possibly add a '*' to the end.  The
814  * new string is returned.
815  */
816 static char *
817 add_glob(str, slen)
818 	const char *str;
819 	int slen;
820 {
821 	char *toglob;
822 	char *s;
823 	bool_t saw_slash = FALSE;
824 
825 	if (slen < 0)
826 		return (char *) 0;
827 
828 	toglob = str_nsave(str, slen + 1, ATEMP); /* + 1 for "*" */
829 	toglob[slen] = '\0';
830 
831 	/*
832 	 * If the pathname contains a wildcard (an unquoted '*',
833 	 * '?', or '[') or parameter expansion ('$'), or a ~username
834 	 * with no trailing slash, then it is globbed based on that
835 	 * value (i.e., without the appended '*').
836 	 */
837 	for (s = toglob; *s; s++) {
838 		if (*s == '\\' && s[1])
839 			s++;
840 		else if (*s == '*' || *s == '[' || *s == '?' || *s == '$'
841 			 || (s[1] == '(' /*)*/ && strchr("*+?@!", *s)))
842 			break;
843 		else if (ISDIRSEP(*s))
844 			saw_slash = TRUE;
845 	}
846 	if (!*s && (*toglob != '~' || saw_slash)) {
847 		toglob[slen] = '*';
848 		toglob[slen + 1] = '\0';
849 	}
850 
851 	return toglob;
852 }
853 
854 /*
855  * Find longest common prefix
856  */
857 int
858 x_longest_prefix(nwords, words)
859 	int nwords;
860 	char *const *words;
861 {
862 	int i, j;
863 	int prefix_len;
864 	char *p;
865 
866 	if (nwords <= 0)
867 		return 0;
868 
869 	prefix_len = strlen(words[0]);
870 	for (i = 1; i < nwords; i++)
871 		for (j = 0, p = words[i]; j < prefix_len; j++)
872 			if (FILECHCONV(p[j]) != FILECHCONV(words[0][j])) {
873 				prefix_len = j;
874 				break;
875 			}
876 	return prefix_len;
877 }
878 
879 void
880 x_free_words(nwords, words)
881 	int nwords;
882 	char **words;
883 {
884 	int i;
885 
886 	for (i = 0; i < nwords; i++)
887 		if (words[i])
888 			afree(words[i], ATEMP);
889 	afree(words, ATEMP);
890 }
891 
892 /* Return the offset of the basename of string s (which ends at se - need not
893  * be null terminated).  Trailing slashes are ignored.  If s is just a slash,
894  * then the offset is 0 (actually, length - 1).
895  *	s		Return
896  *	/etc		1
897  *	/etc/		1
898  *	/etc//		1
899  *	/etc/fo		5
900  *	foo		0
901  *	///		2
902  *			0
903  */
904 int
905 x_basename(s, se)
906 	const char *s;
907 	const char *se;
908 {
909 	const char *p;
910 
911 	if (se == (char *) 0)
912 		se = s + strlen(s);
913 	if (s == se)
914 		return 0;
915 
916 	/* Skip trailing slashes */
917 	for (p = se - 1; p > s && ISDIRSEP(*p); p--)
918 		;
919 	for (; p > s && !ISDIRSEP(*p); p--)
920 		;
921 	if (ISDIRSEP(*p) && p + 1 < se)
922 		p++;
923 
924 	return p - s;
925 }
926 
927 /*
928  *  Apply pattern matching to a table: all table entries that match a pattern
929  * are added to wp.
930  */
931 static void
932 glob_table(pat, wp, tp)
933 	const char *pat;
934 	XPtrV *wp;
935 	struct table *tp;
936 {
937 	struct tstate ts;
938 	struct tbl *te;
939 
940 	for (twalk(&ts, tp); (te = tnext(&ts)); ) {
941 		if (gmatch(te->name, pat, FALSE))
942 			XPput(*wp, str_save(te->name, ATEMP));
943 	}
944 }
945 
946 static void
947 glob_path(flags, pat, wp, path)
948 	int flags;
949 	const char *pat;
950 	XPtrV *wp;
951 	const char *path;
952 {
953 	const char *sp, *p;
954 	char *xp;
955 	int pathlen;
956 	int patlen;
957 	int oldsize, newsize, i, j;
958 	char **words;
959 	XString xs;
960 
961 	patlen = strlen(pat) + 1;
962 	sp = path;
963 	Xinit(xs, xp, patlen + 128, ATEMP);
964 	while (sp) {
965 		xp = Xstring(xs, xp);
966 		if (!(p = strchr(sp, PATHSEP)))
967 			p = sp + strlen(sp);
968 		pathlen = p - sp;
969 		if (pathlen) {
970 			/* Copy sp into xp, stuffing any MAGIC characters
971 			 * on the way
972 			 */
973 			const char *s = sp;
974 
975 			XcheckN(xs, xp, pathlen * 2);
976 			while (s < p) {
977 				if (ISMAGIC(*s))
978 					*xp++ = MAGIC;
979 				*xp++ = *s++;
980 			}
981 			*xp++ = DIRSEP;
982 			pathlen++;
983 		}
984 		sp = p;
985 		XcheckN(xs, xp, patlen);
986 		memcpy(xp, pat, patlen);
987 
988 		oldsize = XPsize(*wp);
989 		glob_str(Xstring(xs, xp), wp, 0);
990 		newsize = XPsize(*wp);
991 
992 		/* Check that each match is executable... */
993 		words = (char **) XPptrv(*wp);
994 		for (i = j = oldsize; i < newsize; i++) {
995 			if (search_access(words[i], X_OK, (int *) 0) >= 0) {
996 				words[j] = words[i];
997 				if (!(flags & XCF_FULLPATH))
998 					memmove(words[j], words[j] + pathlen,
999 						strlen(words[j] + pathlen) + 1);
1000 				j++;
1001 			} else
1002 				afree(words[i], ATEMP);
1003 		}
1004 		wp->cur = (void **) &words[j];
1005 
1006 		if (!*sp++)
1007 			break;
1008 	}
1009 	Xfree(xs, xp);
1010 }
1011 
1012 #endif /* EDIT */
1013