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