1 /* $Header: /src/pub/tcsh/tw.parse.c,v 3.92 2002/06/25 19:02:12 christos Exp $ */
3 * tw.parse.c: Everyone has taken a shot in this futile effort to
4 * lexically analyze a csh line... Well we cannot good
5 * a job as good as sh.lex.c; but we try. Amazing that
6 * it works considering how many hands have touched this code
9 * Copyright (c) 1980, 1991 The Regents of the University of California.
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 RCSID("$Id: tw.parse.c,v 3.92 2002/06/25 19:02:12 christos Exp $")
46 #endif /* WINNT_NATIVE */
47 #define EVEN(x) (((x) & 1) != 1)
49 #define DOT_NONE 0 /* Don't display dot files */
50 #define DOT_NOT 1 /* Don't display dot or dot-dot */
51 #define DOT_ALL 2 /* Display all dot files */
53 /* TW_NONE, TW_COMMAND, TW_VARIABLE, TW_LOGNAME, */
54 /* TW_FILE, TW_DIRECTORY, TW_VARLIST, TW_USER, */
55 /* TW_COMPLETION, TW_ALIAS, TW_SHELLVAR, TW_ENVVAR, */
56 /* TW_BINDING, TW_WORDLIST, TW_LIMIT, TW_SIGNAL */
57 /* TW_JOB, TW_EXPLAIN, TW_TEXT, TW_GRPNAME */
58 static void (*tw_start_entry
[]) __P((DIR *, Char
*)) = {
59 tw_file_start
, tw_cmd_start
, tw_var_start
, tw_logname_start
,
60 tw_file_start
, tw_file_start
, tw_vl_start
, tw_logname_start
,
61 tw_complete_start
, tw_alias_start
, tw_var_start
, tw_var_start
,
62 tw_bind_start
, tw_wl_start
, tw_limit_start
, tw_sig_start
,
63 tw_job_start
, tw_file_start
, tw_file_start
, tw_grpname_start
66 static Char
* (*tw_next_entry
[]) __P((Char
*, int *)) = {
67 tw_file_next
, tw_cmd_next
, tw_var_next
, tw_logname_next
,
68 tw_file_next
, tw_file_next
, tw_var_next
, tw_logname_next
,
69 tw_var_next
, tw_var_next
, tw_shvar_next
, tw_envvar_next
,
70 tw_bind_next
, tw_wl_next
, tw_limit_next
, tw_sig_next
,
71 tw_job_next
, tw_file_next
, tw_file_next
, tw_grpname_next
74 static void (*tw_end_entry
[]) __P((void)) = {
75 tw_dir_end
, tw_dir_end
, tw_dir_end
, tw_logname_end
,
76 tw_dir_end
, tw_dir_end
, tw_dir_end
, tw_logname_end
,
77 tw_dir_end
, tw_dir_end
, tw_dir_end
, tw_dir_end
,
78 tw_dir_end
, tw_dir_end
, tw_dir_end
, tw_dir_end
,
79 tw_dir_end
, tw_dir_end
, tw_dir_end
, tw_grpname_end
84 /* Set to TRUE if recexact is set and an exact match is found
85 * along with other, longer, matches.
90 int match_unique_match
= FALSE
;
91 int non_unique_match
= FALSE
;
92 static bool SearchNoDirErr
= 0; /* t_search returns -2 if dir is unreadable */
94 /* state so if a completion is interrupted, the input line doesn't get
96 int InsideCompletion
= 0;
98 /* do the expand or list on the command line -- SHOULD BE REPLACED */
100 extern Char NeedsRedraw
; /* from ed.h */
101 extern int Tty_raw_mode
;
102 extern int TermH
; /* from the editor routines */
103 extern int lbuffed
; /* from sh.print.c */
105 static void extract_dir_and_name
__P((Char
*, Char
*, Char
*));
106 static int insert_meta
__P((Char
*, Char
*, Char
*, bool));
107 static Char
*tilde
__P((Char
*, Char
*));
109 static int expand_dir
__P((Char
*, Char
*, DIR **, COMMAND
));
111 static bool nostat
__P((Char
*));
112 static Char filetype
__P((Char
*, Char
*));
113 static int t_glob
__P((Char
***, int));
114 static int c_glob
__P((Char
***));
115 static int is_prefix
__P((Char
*, Char
*));
116 static int is_prefixmatch
__P((Char
*, Char
*, int));
117 static int is_suffix
__P((Char
*, Char
*));
118 static int recognize
__P((Char
*, Char
*, int, int, int));
119 static int ignored
__P((Char
*));
120 static int isadirectory
__P((Char
*, Char
*));
122 static int tw_collect_items
__P((COMMAND
, int, Char
*, Char
*,
123 Char
*, Char
*, int));
124 static int tw_collect
__P((COMMAND
, int, Char
*, Char
*,
125 Char
**, Char
*, int, DIR *));
127 static Char tw_suffix
__P((int, Char
*, Char
*, Char
*,
129 static void tw_fixword
__P((int, Char
*, Char
*, Char
*, int));
130 static void tw_list_items
__P((int, int, int));
131 static void add_scroll_tab
__P((Char
*));
132 static void choose_scroll_tab
__P((Char
**, int));
133 static void free_scroll_tab
__P((void));
134 static int find_rows
__P((Char
*[], int, int));
138 * If we find a set command, then we break a=b to a= and word becomes
139 * b else, we don't break a=b. [don't use that; splits words badly and
140 * messes up tw_complete()]
142 #define isaset(c, w) ((w)[-1] == '=' && \
143 ((c)[0] == 's' && (c)[1] == 'e' && (c)[2] == 't' && \
144 ((c[3] == ' ' || (c)[3] == '\t'))))
147 #define QLINESIZE (INBUFSIZE + 1)
149 /* TRUE if character must be quoted */
150 #define tricky(w) (cmap(w, _META | _DOL | _QF | _QB | _ESC | _GLOB) && w != '#')
151 /* TRUE if double quotes don't protect character */
152 #define tricky_dq(w) (cmap(w, _DOL | _QB))
156 * > 1: No. of items found
157 * = 1: Exactly one match / spelling corrected
158 * = 0: No match / spelling was correct
159 * < 0: Error (incl spelling correction impossible)
162 tenematch(inputline
, num_read
, command
)
163 Char
*inputline
; /* match string prefix */
164 int num_read
; /* # actually in inputline */
165 COMMAND command
; /* LIST or RECOGNIZE or PRINT_HELP */
168 Char qline
[QLINESIZE
];
169 Char qu
= 0, *pat
= STRNULL
;
170 Char
*str_end
, *cp
, *wp
, *wordp
;
171 Char
*cmd_start
, *word_start
, *word
;
172 Char
*ocmd_start
= NULL
, *oword_start
= NULL
, *oword
= NULL
;
175 int looking
; /* what we are looking for */
176 int search_ret
; /* what search returned for debugging */
179 if (num_read
> QLINESIZE
- 1)
181 str_end
= &inputline
[num_read
];
183 word_start
= inputline
;
184 word
= cmd_start
= wp
= qline
;
185 for (cp
= inputline
; cp
< str_end
; cp
++) {
186 if (!cmap(qu
, _ESC
)) {
187 if (cmap(*cp
, _QF
|_ESC
)) {
188 if (qu
== 0 || qu
== *cp
) {
193 if (qu
!= '\'' && cmap(*cp
, _QB
)) {
194 if ((backq
^= 1) != 0) {
195 ocmd_start
= cmd_start
;
196 oword_start
= word_start
;
199 word
= cmd_start
= wp
+ 1;
202 cmd_start
= ocmd_start
;
203 word_start
= oword_start
;
213 /* Don't quote '/' to make the recognize stuff work easily */
214 /* Don't quote '$' in double quotes */
216 if (cmap(*cp
, _ESC
) && cp
< str_end
- 1 && cp
[1] == HIST
)
218 else if (qu
&& (tricky(*cp
) || *cp
== '~') && !(qu
== '\"' && tricky_dq(*cp
)))
222 if (ismetahash(*wp
) /* || isaset(cmd_start, wp + 1) */)
223 word
= wp
+ 1, word_start
= cp
+ 1;
232 * Avoid a nasty message from the RTU 4.1A & RTU 5.0 compiler concerning
233 * the "overuse of registers". According to the compiler release notes,
234 * incorrect code may be produced unless the offending expression is
235 * rewritten. Therefore, we can't just ignore it, DAS DEC-90.
237 space_left
= QLINESIZE
- 1;
238 space_left
-= word
- qline
;
240 space_left
= QLINESIZE
- 1 - (int) (word
- qline
);
244 * SPECIAL HARDCODED COMPLETIONS:
245 * first word of command -> TW_COMMAND
246 * everything else -> TW_ZERO
249 looking
= starting_a_command(word
- 1, qline
) ?
250 TW_COMMAND
: TW_ZERO
;
255 xprintf(CGETS(30, 1, "starting_a_command %d\n"), looking
);
256 xprintf("\ncmd_start:%S:\n", cmd_start
);
257 xprintf("qline:%S:\n", qline
);
259 for (wp
= qline
; *wp
; wp
++)
260 xprintf("%c", *wp
& QUOTE
? '-' : ' ');
262 xprintf("word:%S:\n", word
);
264 /* Must be last, so wp is still pointing to the end of word */
265 for (wp
= word
; *wp
; wp
++)
266 xprintf("%c", *wp
& QUOTE
? '-' : ' ');
270 if ((looking
== TW_COMMAND
|| looking
== TW_ZERO
) &&
271 (command
== RECOGNIZE
|| command
== LIST
|| command
== SPELL
||
272 command
== RECOGNIZE_SCROLL
)) {
274 xprintf(CGETS(30, 2, "complete %d "), looking
);
276 looking
= tw_complete(cmd_start
, &wordp
, &pat
, looking
, &suf
);
278 xprintf(CGETS(30, 3, "complete %d %S\n"), looking
, pat
);
283 Char buffer
[FILSIZ
+ 1], *bptr
;
285 Char
*items
[2], **ptr
;
289 case RECOGNIZE_SCROLL
:
291 if (adrof(STRautocorrect
)) {
292 if ((slshp
= Strrchr(wordp
, '/')) != NULL
&& slshp
[1] != '\0') {
294 for (bptr
= wordp
; bptr
< slshp
; bptr
++) {
296 * do not try to correct spelling of words containing
297 * globbing characters
308 search_ret
= t_search(wordp
, wp
, command
, space_left
, looking
, 1,
312 if (search_ret
== -2) {
313 Char rword
[FILSIZ
+ 1];
315 (void) Strcpy(rword
, slshp
);
316 if (slshp
!= STRNULL
)
318 search_ret
= spell_me(wordp
, QLINESIZE
- (wordp
- qline
), looking
,
320 if (search_ret
== 1) {
321 (void) Strcat(wordp
, rword
);
322 wp
= wordp
+ (int) Strlen(wordp
);
323 search_ret
= t_search(wordp
, wp
, command
, space_left
,
324 looking
, 1, pat
, suf
);
327 if (*wp
&& insert_meta(word_start
, str_end
, word
, !qu
) < 0)
328 return -1; /* error inserting */
332 for (bptr
= word_start
; bptr
< str_end
; bptr
++) {
334 * do not try to correct spelling of words containing globbing
340 search_ret
= spell_me(wordp
, QLINESIZE
- (wordp
- qline
), looking
,
342 if (search_ret
== 1) {
343 if (insert_meta(word_start
, str_end
, word
, !qu
) < 0)
344 return -1; /* error inserting */
354 (void) Strncpy(buffer
, wordp
, FILSIZ
+ 1);
358 count
= (looking
== TW_COMMAND
&& Strchr(wordp
, '/') == 0) ?
360 t_glob(&ptr
, looking
== TW_COMMAND
);
363 print_by_column(STRNULL
, ptr
, count
, 0);
365 DeleteBack(str_end
- word_start
);/* get rid of old word */
366 for (i
= 0; i
< count
; i
++)
367 if (ptr
[i
] && *ptr
[i
]) {
368 (void) quote(ptr
[i
]);
369 if (insert_meta(0, 0, ptr
[i
], 0) < 0 ||
370 InsertStr(STRspace
) < 0) {
372 return -1; /* error inserting */
381 if (dollar(buffer
, word
)) {
382 if (insert_meta(word_start
, str_end
, buffer
, !qu
) < 0)
383 return -1; /* error inserting */
389 if ((bptr
= dnormalize(wordp
, symlinks
== SYM_IGNORE
||
390 symlinks
== SYM_EXPAND
)) != NULL
) {
391 (void) Strcpy(buffer
, bptr
);
393 if (insert_meta(word_start
, str_end
, buffer
, !qu
) < 0)
394 return -1; /* error inserting */
399 case COMMAND_NORMALIZE
:
400 if (!cmd_expand(wordp
, buffer
))
402 if (insert_meta(word_start
, str_end
, buffer
, !qu
) < 0)
403 return -1; /* error inserting */
408 search_ret
= t_search(wordp
, wp
, LIST
, space_left
, looking
, 1,
413 xprintf(CGETS(30, 4, "%s: Internal match error.\n"), progname
);
417 } /* end tenematch */
421 * Return a list of files that match the pattern
432 gflag
= 0, tglob(*v
);
434 getexit(osetexit
); /* make sure to come back here */
452 int fwd
, i
, ac
= gargc
;
454 for (i
= 0, fwd
= 0; i
< ac
; i
++)
455 if (!executable(NULL
, av
[i
], 0)) {
475 * Return a list of commands that match the pattern
481 Char
*pat
= **v
, *cmd
, **av
;
482 Char dir
[MAXPATHLEN
+1];
490 av
= (Char
**) xmalloc((size_t) (at
* sizeof(Char
*)));
493 tw_cmd_start(NULL
, NULL
);
494 while ((cmd
= tw_cmd_next(dir
, &flag
)) != NULL
)
495 if (Gmatch(cmd
, pat
)) {
498 av
= (Char
**) xrealloc((ptr_t
) av
,
499 (size_t) (at
* sizeof(Char
*)));
501 av
[ac
++] = Strsave(cmd
);
512 * change the word before the cursor.
513 * cp must point to the start of the unquoted word.
514 * cpend to the end of it.
515 * word is the text that has to be substituted.
517 * try to keep all the quote characters of the user's input.
518 * change quote type only if necessary.
521 insert_meta(cp
, cpend
, word
, closequotes
)
527 Char buffer
[2 * FILSIZ
+ 1], *bptr
, *wptr
;
528 int in_sync
= (cp
!= NULL
);
530 int ndel
= (int) (cp
? cpend
- cp
: 0);
534 #endif /* DSPMBYTE */
536 for (bptr
= buffer
, wptr
= word
;;) {
537 if (bptr
> buffer
+ 2 * FILSIZ
- 5)
544 #endif /* DSPMBYTE */
545 if (in_sync
&& !cmap(qu
, _ESC
) && cmap(*cp
, _QF
|_ESC
))
546 if (qu
== 0 || qu
== *cp
) {
561 #endif /* DSPMBYTE */
562 if (cmap(w
, _ESC
| _QF
))
563 wq
= QUOTE
; /* quotes are always quoted */
565 if (!wq
&& qu
&& tricky(w
) && !(qu
== '\"' && tricky_dq(w
))) {
566 /* We have to unquote the character */
578 } else if (qu
&& w
== qu
) {
580 if (bptr
> buffer
&& bptr
[-1] == qu
) {
581 /* User misunderstanding :) */
592 else if (wq
&& qu
== '\"' && tricky_dq(w
)) {
598 } else if (wq
&& ((!qu
&& (tricky(w
) || (w
== HISTSUB
&& bptr
== buffer
))) || (!cmap(qu
, _ESC
) && w
== HIST
))) {
605 #endif /* DSPMBYTE */
606 if (in_sync
&& *cp
++ != w
)
610 if (mbytepos
== 1 && Ismbyte1(w
))
614 #endif /* DSPMBYTE */
620 if (closequotes
&& qu
&& !cmap(qu
, _ESC
))
625 return InsertStr(buffer
);
626 } /* end insert_meta */
631 * return true if check matches initial chars in template
632 * This differs from PWB imatch in that if check is null
633 * it matches anything
636 is_prefix(check
, template)
637 register Char
*check
, *template;
639 for (; *check
; check
++, template++)
640 if ((*check
& TRIM
) != (*template & TRIM
))
643 } /* end is_prefix */
647 * return true if check matches initial chars in template
648 * This differs from PWB imatch in that if check is null
649 * it matches anything
650 * and matches on shortening of commands
653 is_prefixmatch(check
, template, igncase
)
654 Char
*check
, *template;
659 for (; *check
; check
++, template++) {
660 if ((*check
& TRIM
) != (*template & TRIM
)) {
661 MCH1
= (*check
& TRIM
);
662 MCH2
= (*template & TRIM
);
663 MCH1
= Isupper(MCH1
) ? Tolower(MCH1
) : MCH1
;
664 MCH2
= Isupper(MCH2
) ? Tolower(MCH2
) : MCH2
;
666 if (!igncase
&& ((*check
& TRIM
) == '-' ||
667 (*check
& TRIM
) == '.' ||
668 (*check
& TRIM
) == '_')) {
669 MCH1
= MCH2
= (*check
& TRIM
);
672 } else if (MCH1
== '-') {
675 for (;*template && (*template & TRIM
) != MCH1
&&
676 (*template & TRIM
) != MCH2
; template++)
688 } /* end is_prefixmatch */
692 * Return true if the chars in template appear at the
693 * end of check, I.e., are it's suffix.
696 is_suffix(check
, template)
697 register Char
*check
, *template;
699 register Char
*t
, *c
;
701 for (t
= template; *t
++;)
703 for (c
= check
; *c
++;)
708 if (c
== check
|| (*--t
& TRIM
) != (*--c
& TRIM
))
711 } /* end is_suffix */
715 * Return true if this is an ignored item
724 if ((vp
= adrof(STRfignore
)) == NULL
|| (cp
= vp
->vec
) == NULL
)
726 for (; *cp
!= NULL
; cp
++)
727 if (is_suffix(item
, *cp
))
734 /* starting_a_command():
735 * return true if the command starting at wordstart is a command
738 starting_a_command(wordstart
, inputline
)
739 register Char
*wordstart
, *inputline
;
741 register Char
*ptr
, *ncmdstart
;
744 cmdstart
[] = {'`', ';', '&', '(', '|', '\0'},
745 cmdalive
[] = {' ', '\t', '\'', '"', '<', '>', '\0'};
748 * Find if the number of backquotes is odd or even.
750 for (ptr
= wordstart
, count
= 0;
752 count
+= (*ptr
-- == '`'))
755 * if the number of backquotes is even don't include the backquote char in
756 * the list of command starting delimiters [if it is zero, then it does not
759 ncmdstart
= cmdstart
+ EVEN(count
);
762 * look for the characters previous to this word if we find a command
763 * starting delimiter we break. if we find whitespace and another previous
764 * word then we are not a command
766 * count is our state machine: 0 looking for anything 1 found white-space
769 for (count
= 0; wordstart
>= inputline
; wordstart
--) {
770 if (*wordstart
== '\0')
772 if (Strchr(ncmdstart
, *wordstart
))
777 if ((ptr
= Strchr(cmdalive
, *wordstart
)) != NULL
)
779 if (count
== 1 && !ptr
)
783 if (wordstart
> inputline
)
784 switch (*wordstart
) {
785 case '&': /* Look for >& */
786 while (wordstart
> inputline
&&
787 (*--wordstart
== ' ' || *wordstart
== '\t'))
789 if (*wordstart
== '>')
792 case '(': /* check for foreach, if etc. */
793 while (wordstart
> inputline
&&
794 (*--wordstart
== ' ' || *wordstart
== '\t'))
796 if (!iscmdmeta(*wordstart
) &&
797 (*wordstart
!= ' ' && *wordstart
!= '\t'))
804 } /* end starting_a_command */
808 * Object: extend what user typed up to an ambiguity.
810 * On first match, copy full item (assume it'll be the only match)
811 * On subsequent matches, shorten exp_name to the first
812 * character mismatch between exp_name and item.
813 * If we shorten it back to the prefix length, stop searching.
816 recognize(exp_name
, item
, name_length
, numitems
, enhanced
)
817 Char
*exp_name
, *item
;
818 int name_length
, numitems
, enhanced
;
821 register Char
*x
, *ent
;
822 register int len
= 0;
826 igncase
= (vp
= adrof(STRcomplete
)) != NULL
&& vp
->vec
!= NULL
&&
827 Strcmp(*(vp
->vec
), STRigncase
) == 0;
828 #endif /* WINNT_NATIVE */
830 if (numitems
== 1) { /* 1st match */
831 copyn(exp_name
, item
, MAXNAMLEN
);
837 #endif /* WINNT_NATIVE */
839 for (x
= exp_name
, ent
= item
; *x
&& (*x
& TRIM
) == (*ent
& TRIM
); x
++, ent
++)
842 for (x
= exp_name
, ent
= item
; *x
; x
++, ent
++) {
845 MCH1
= Isupper(MCH1
) ? Tolower(MCH1
) : MCH1
;
846 MCH2
= Isupper(MCH2
) ? Tolower(MCH2
) : MCH2
;
851 if (*x
|| !*ent
) /* Shorter or exact match */
852 copyn(exp_name
, item
, MAXNAMLEN
);
854 *x
= '\0'; /* Shorten at 1st char diff */
855 if (!(match_unique_match
|| is_set(STRrecexact
) || (enhanced
&& *ent
)) && len
== name_length
) /* Ambiguous to prefix? */
856 return (-1); /* So stop now and save time */
858 } /* end recognize */
861 /* tw_collect_items():
862 * Collect items that match target.
864 * Returns the spelling distance of the closest match.
866 * Returns the number of items found.
867 * If none found, but some ignored items were found,
868 * It returns the -number of ignored items.
871 tw_collect_items(command
, looking
, exp_dir
, exp_name
, target
, pat
, flags
)
874 Char
*exp_dir
, *exp_name
, *target
, *pat
;
878 int done
= FALSE
; /* Search is done */
879 int showdots
; /* Style to show dot files */
880 int nignored
= 0; /* Number of fignored items */
881 int numitems
= 0; /* Number of matched items */
882 int name_length
= (int) Strlen(target
); /* Length of prefix (file name) */
883 int exec_check
= flags
& TW_EXEC_CHK
;/* need to check executability */
884 int dir_check
= flags
& TW_DIR_CHK
; /* Need to check for directories */
885 int text_check
= flags
& TW_TEXT_CHK
;/* Need to check for non-directories */
886 int dir_ok
= flags
& TW_DIR_OK
; /* Ignore directories? */
887 int gpat
= flags
& TW_PAT_OK
; /* Match against a pattern */
888 int ignoring
= flags
& TW_IGN_OK
; /* Use fignore? */
889 int d
= 4, nd
; /* Spelling distance */
891 Char buf
[MAXPATHLEN
+1];
901 if ((ptr
= varval(STRlistflags
)) != STRNULL
)
914 while (!done
&& (item
= (*tw_next_entry
[looking
])(exp_dir
, &flags
))) {
916 xprintf("item = %S\n", item
);
923 * Don't match . files on null prefix match
925 if (showdots
== DOT_NOT
&& (ISDOT(item
) || ISDOTDOT(item
)))
927 if (name_length
== 0 && item
[0] == '.' && showdots
== DOT_NONE
)
932 exec_check
= flags
& TW_EXEC_CHK
;
933 dir_ok
= flags
& TW_DIR_OK
;
947 case SPELL
: /* correct the spelling of the last bit */
948 if (name_length
== 0) {/* zero-length word can't be misspelled */
949 exp_name
[0] = '\0';/* (not trying is important for ~) */
954 if (gpat
&& !Gmatch(item
, pat
))
957 * Swapped the order of the spdist() arguments as suggested
958 * by eeide@asylum.cs.utah.edu (Eric Eide)
960 nd
= spdist(target
, item
); /* test the item against original */
961 if (nd
<= d
&& nd
!= 4) {
962 if (!(exec_check
&& !executable(exp_dir
, item
, dir_ok
))) {
963 (void) Strcpy(exp_name
, item
);
965 if (d
== 0) /* if found it exactly */
970 if (spdir(exp_name
, exp_dir
, item
, target
)) {
971 if (exec_check
&& !executable(exp_dir
, exp_name
, dir_ok
))
975 * We don't want to stop immediately, because
976 * we might find an exact/better match later.
989 case RECOGNIZE_SCROLL
:
992 igncase
= (vp
= adrof(STRcomplete
)) != NULL
&& vp
->vec
!= NULL
&&
993 Strcmp(*(vp
->vec
), STRigncase
) == 0;
994 #endif /* WINNT_NATIVE */
995 enhanced
= (vp
= adrof(STRcomplete
)) != NULL
&& !Strcmp(*(vp
->vec
),STRenhance
);
996 if (enhanced
|| igncase
) {
997 if (!is_prefixmatch(target
, item
, igncase
))
1000 if (!is_prefix(target
, item
))
1004 if (exec_check
&& !executable(exp_dir
, item
, dir_ok
))
1007 if (dir_check
&& !isadirectory(exp_dir
, item
))
1010 if (text_check
&& isadirectory(exp_dir
, item
))
1014 * Only pattern match directories if we're checking
1017 if (gpat
&& !Gmatch(item
, pat
) &&
1018 (dir_check
|| !isadirectory(exp_dir
, item
)))
1022 * Remove duplicates in command listing and completion
1023 * AFEB added code for TW_LOGNAME and TW_USER cases
1025 if (looking
== TW_COMMAND
|| looking
== TW_LOGNAME
1026 || looking
== TW_USER
|| command
== LIST
) {
1027 copyn(buf
, item
, MAXPATHLEN
);
1028 len
= (int) Strlen(buf
);
1031 if (!(dir_ok
&& exec_check
))
1033 if (filetype(exp_dir
, item
) == '/') {
1041 buf
[len
++] = filetype(exp_dir
, item
);
1048 if ((looking
== TW_COMMAND
|| looking
== TW_USER
1049 || looking
== TW_LOGNAME
) && tw_item_find(buf
))
1052 /* maximum length 1 (NULL) + 1 (~ or $) + 1 (filetype) */
1053 ptr
= tw_item_add(len
+ 3);
1054 copyn(ptr
, buf
, MAXPATHLEN
);
1055 if (command
== LIST
)
1060 if (command
== RECOGNIZE
|| command
== RECOGNIZE_ALL
||
1061 command
== RECOGNIZE_SCROLL
) {
1062 if (ignoring
&& ignored(item
)) {
1066 else if (command
== RECOGNIZE_SCROLL
) {
1067 add_scroll_tab(item
);
1071 if (match_unique_match
|| is_set(STRrecexact
)) {
1072 if (StrQcmp(target
, item
) == 0) { /* EXACT match */
1073 copyn(exp_name
, item
, MAXNAMLEN
);
1074 numitems
= 1; /* fake into expanding */
1075 non_unique_match
= TRUE
;
1080 if (recognize(exp_name
, item
, name_length
, ++numitems
, enhanced
))
1081 if (command
!= RECOGNIZE_SCROLL
)
1083 if (enhanced
&& (int)Strlen(exp_name
) < name_length
)
1084 copyn(exp_name
, target
, MAXNAMLEN
);
1092 xprintf("done item = %S\n", item
);
1097 if (command
== RECOGNIZE_SCROLL
) {
1098 if ((cnt
<= curchoice
) || (curchoice
== -1)) {
1102 } else if (numitems
> 1) {
1104 curchoice
= cnt
- 1;
1105 choose_scroll_tab(&exp_name
, cnt
);
1111 if (command
== SPELL
)
1114 if (ignoring
&& numitems
== 0 && nignored
> 0)
1123 * Find and return the appropriate suffix character
1127 tw_suffix(looking
, exp_dir
, exp_name
, target
, name
)
1129 Char
*exp_dir
, *exp_name
, *target
, *name
;
1135 (void) strip(exp_name
);
1144 * Don't consider array variables or empty variables
1146 if ((vp
= adrof(exp_name
)) != NULL
&& vp
->vec
!= NULL
) {
1147 if ((ptr
= vp
->vec
[0]) == NULL
|| *ptr
== '\0' ||
1151 else if ((ptr
= tgetenv(exp_name
)) == NULL
|| *ptr
== '\0')
1156 return isadirectory(exp_dir
, ptr
) ? '/' : ' ';
1164 return isadirectory(exp_dir
, exp_name
) ? '/' : ' ';
1184 } /* end tw_suffix */
1188 * Repair a word after a spalling or a recognizwe
1191 tw_fixword(looking
, word
, dir
, exp_name
, max_word_length
)
1193 Char
*word
, *dir
, *exp_name
;
1194 int max_word_length
;
1200 copyn(word
, STRtilde
, 1);
1204 if ((ptr
= Strrchr(word
, '$')) != NULL
)
1205 *++ptr
= '\0'; /* Delete after the dollar */
1213 copyn(word
, dir
, max_word_length
); /* put back dir part */
1221 (void) quote(exp_name
);
1222 catn(word
, exp_name
, max_word_length
); /* add extended name */
1223 } /* end tw_fixword */
1227 * Collect items. Return -1 in case we were interrupted or
1228 * the return value of tw_collect
1229 * This is really a wrapper for tw_collect_items, serving two
1231 * 1. Handles interrupt cleanups.
1232 * 2. Retries if we had no matches, but there were ignored matches
1235 tw_collect(command
, looking
, exp_dir
, exp_name
, target
, pat
, flags
, dir_fd
)
1238 Char
*exp_dir
, *exp_name
, **target
, *pat
;
1242 static int ni
; /* static so we don't get clobbered */
1246 xprintf("target = %S\n", *target
);
1251 (*tw_start_entry
[looking
])(dir_fd
, pat
);
1252 InsideCompletion
= 1;
1254 /* interrupted, clean up */
1256 InsideCompletion
= 0;
1259 #if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__)
1260 /* Compiler bug? (from PWP) */
1261 if ((looking
== TW_LOGNAME
) || (looking
== TW_USER
))
1264 if (looking
== TW_GRPNAME
)
1268 #else /* !(SOLARIS2 && i386 && !__GNUC__) */
1269 (*tw_end_entry
[looking
])();
1270 #endif /* !(SOLARIS2 && i386 && !__GNUC__) */
1275 if ((ni
= tw_collect_items(command
, looking
, exp_dir
, exp_name
,
1278 flags
& ~TW_IGN_OK
)) >= 0) {
1280 InsideCompletion
= 0;
1282 #if defined(SOLARIS2) && defined(i386) && !defined(__GNUC__)
1283 /* Compiler bug? (from PWP) */
1284 if ((looking
== TW_LOGNAME
) || (looking
== TW_USER
))
1287 if (looking
== TW_GRPNAME
)
1291 #else /* !(SOLARIS2 && i386 && !__GNUC__) */
1292 (*tw_end_entry
[looking
])();
1293 #endif /* !(SOLARIS2 && i386 && !__GNUC__) */
1298 } /* end tw_collect */
1302 * List the items that were found
1304 * NOTE instead of looking at numerical vars listmax and listmaxrows
1305 * we can look at numerical var listmax, and have a string value
1306 * listmaxtype (or similar) than can have values 'items' and 'rows'
1307 * (by default interpreted as 'items', for backwards compatibility)
1310 tw_list_items(looking
, numitems
, list_max
)
1311 int looking
, numitems
, list_max
;
1320 if ((ptr
= varval(STRlistmax
)) != STRNULL
) {
1322 if (!Isdigit(*ptr
)) {
1326 max_items
= max_items
* 10 + *ptr
++ - '0';
1328 if ((max_items
> 0) && (numitems
> max_items
) && list_max
)
1329 max_items
= numitems
;
1334 if (max_items
== 0 && (ptr
= varval(STRlistmaxrows
)) != STRNULL
) {
1338 if (!Isdigit(*ptr
)) {
1342 max_rows
= max_rows
* 10 + *ptr
++ - '0';
1344 if (max_rows
!= 0 && looking
!= TW_JOB
)
1345 rows
= find_rows(tw_item_get(), numitems
, TRUE
);
1347 rows
= numitems
; /* underestimate for lines wider than the termH */
1348 if ((max_rows
> 0) && (rows
> max_rows
) && list_max
)
1355 if (max_items
|| max_rows
) {
1361 name
= CGETS(30, 5, "items");
1365 name
= CGETS(30, 6, "rows");
1369 xprintf(CGETS(30, 7, "There are %d %s, list them anyway? [n/y] "),
1372 /* We should be in Rawmode here, so no \n to catch */
1373 (void) read(SHIN
, &tc
, 1);
1374 xprintf("%c\r\n", tc
); /* echo the char, do a newline */
1376 * Perhaps we should use the yesexpr from the
1379 if (strchr(CGETS(30, 13, "Yy"), tc
) == NULL
)
1383 if (looking
!= TW_SIGNAL
)
1384 qsort((ptr_t
) tw_item_get(), (size_t) numitems
, sizeof(Char
*),
1385 (int (*) __P((const void *, const void *))) fcompare
);
1386 if (looking
!= TW_JOB
)
1387 print_by_column(STRNULL
, tw_item_get(), numitems
, TRUE
);
1390 * print one item on every line because jobs can have spaces
1391 * and it is confusing.
1394 Char
**w
= tw_item_get();
1396 for (i
= 0; i
< numitems
; i
++) {
1397 xprintf("%S", w
[i
]);
1403 } /* end tw_list_items */
1407 * Perform a RECOGNIZE, LIST or SPELL command on string "word".
1410 * >= 0: SPELL command: "distance" (see spdist())
1411 * other: No. of items found
1412 * < 0: Error (message or beep is output)
1416 t_search(word
, wp
, command
, max_word_length
, looking
, list_max
, pat
, suf
)
1417 Char
*word
, *wp
; /* original end-of-word */
1419 int max_word_length
, looking
, list_max
;
1423 int numitems
, /* Number of items matched */
1424 flags
= 0, /* search flags */
1425 gpat
= pat
[0] != '\0', /* Glob pattern search */
1426 nd
; /* Normalized directory return */
1427 Char exp_dir
[FILSIZ
+ 1], /* dir after ~ expansion */
1428 dir
[FILSIZ
+ 1], /* /x/y/z/ part in /x/y/z/f */
1429 exp_name
[MAXNAMLEN
+ 1], /* the recognized (extended) */
1430 name
[MAXNAMLEN
+ 1], /* f part in /d/d/d/f name */
1431 *target
; /* Target to expand/correct/list */
1437 * bugfix by Marty Grossman (grossman@CC5.BBN.COM): directory listing can
1438 * dump core when interrupted
1442 non_unique_match
= FALSE
; /* See the recexact code below */
1444 extract_dir_and_name(word
, dir
, name
);
1447 * SPECIAL HARDCODED COMPLETIONS:
1448 * foo$variable -> TW_VARIABLE
1449 * ~user -> TW_LOGNAME
1452 if ((*word
== '~') && (Strchr(word
, '/') == NULL
)) {
1453 looking
= TW_LOGNAME
;
1455 gpat
= 0; /* Override pattern mechanism */
1457 else if ((target
= Strrchr(name
, '$')) != 0 &&
1458 (Strchr(name
, '/') == NULL
)) {
1460 looking
= TW_VARIABLE
;
1461 gpat
= 0; /* Override pattern mechanism */
1467 * Try to figure out what we should be looking for
1469 if (looking
& TW_PATH
) {
1470 gpat
= 0; /* pattern holds the pathname to be used */
1471 copyn(exp_dir
, pat
, MAXNAMLEN
);
1472 if (exp_dir
[Strlen(exp_dir
) - 1] != '/')
1473 catn(exp_dir
, STRslash
, MAXNAMLEN
);
1474 catn(exp_dir
, dir
, MAXNAMLEN
);
1479 switch (looking
& ~TW_PATH
) {
1488 if (Strchr(word
, '/') || (looking
& TW_PATH
)) {
1490 flags
|= TW_EXEC_CHK
;
1494 /* PWP: don't even bother when doing ALL of the commands */
1495 if (looking
== TW_COMMAND
&& (*word
== '\0'))
1503 gpat
= 0; /* pattern holds the name of the variable */
1507 if (command
== LIST
&& pat
!= NULL
) {
1520 * let fignore work only when we are not using a pattern
1522 flags
|= (gpat
== 0) ? TW_IGN_OK
: TW_PAT_OK
;
1525 xprintf(CGETS(30, 8, "looking = %d\n"), looking
);
1542 if ((nd
= expand_dir(dir
, exp_dir
, &dir_fd
, command
)) != 0)
1547 flags
|= TW_DIR_CHK
;
1551 * This is supposed to expand the directory stack.
1554 * 2. directories with the same name
1560 * Supposed to do delayed expansion, but it is inconsistent
1561 * from a user-interface point of view, since it does not
1562 * immediately obey addsuffix
1564 if ((nd
= expand_dir(dir
, exp_dir
, &dir_fd
, command
)) != 0)
1566 if (isadirectory(exp_dir
, name
)) {
1567 if (exp_dir
[0] != '\0' || name
[0] != '\0') {
1568 catn(dir
, name
, MAXNAMLEN
);
1569 if (dir
[Strlen(dir
) - 1] != '/')
1570 catn(dir
, STRslash
, MAXNAMLEN
);
1571 if ((nd
= expand_dir(dir
, exp_dir
, &dir_fd
, command
)) != 0)
1573 if (word
[Strlen(word
) - 1] != '/')
1574 catn(word
, STRslash
, MAXNAMLEN
);
1579 if ((nd
= expand_dir(dir
, exp_dir
, &dir_fd
, command
)) != 0)
1584 flags
|= TW_TEXT_CHK
;
1587 if ((nd
= expand_dir(dir
, exp_dir
, &dir_fd
, command
)) != 0)
1591 case TW_PATH
| TW_TEXT
:
1592 case TW_PATH
| TW_FILE
:
1593 case TW_PATH
| TW_DIRECTORY
:
1594 case TW_PATH
| TW_COMMAND
:
1595 if ((dir_fd
= opendir(short2str(exp_dir
))) == NULL
) {
1596 if (command
== RECOGNIZE
)
1598 xprintf("%S: %s", exp_dir
, strerror(errno
));
1599 if (command
!= RECOGNIZE
)
1604 if (exp_dir
[Strlen(exp_dir
) - 1] != '/')
1605 catn(exp_dir
, STRslash
, MAXNAMLEN
);
1607 looking
&= ~TW_PATH
;
1611 flags
|= TW_TEXT_CHK
;
1618 flags
|= TW_DIR_CHK
;
1622 copyn(target
, word
, MAXNAMLEN
); /* so it can match things */
1626 abort(); /* Cannot happen */
1636 * Check if the spelling was already correct
1637 * From: Rob McMahon <cudcv@cu.warwick.ac.uk>
1639 if (command
== SPELL
&& getpwnam(short2str(word
)) != NULL
) {
1645 copyn(name
, word
, MAXNAMLEN
); /* name sans ~ */
1646 if (looking
== TW_LOGNAME
)
1653 copyn(target
, word
, MAXNAMLEN
); /* so it can match things */
1657 xprintf(CGETS(30, 9,
1658 "\n%s internal error: I don't know what I'm looking for!\n"),
1664 numitems
= tw_collect(command
, looking
, exp_dir
, exp_name
,
1665 &target
, pat
, flags
, dir_fd
);
1672 case RECOGNIZE_SCROLL
:
1676 tw_fixword(looking
, word
, dir
, exp_name
, max_word_length
);
1678 if (!match_unique_match
&& is_set(STRaddsuffix
) && numitems
== 1) {
1683 case 0: /* Automatic suffix */
1684 suffix
[0] = tw_suffix(looking
, exp_dir
, exp_name
, target
, name
);
1687 case -1: /* No suffix */
1690 default: /* completion specified suffix */
1691 suffix
[0] = (Char
) suf
;
1694 catn(word
, suffix
, max_word_length
);
1699 tw_list_items(looking
, numitems
, list_max
);
1704 tw_fixword(looking
, word
, dir
, exp_name
, max_word_length
);
1708 xprintf("Bad tw_command\n");
1711 } /* end t_search */
1714 /* extract_dir_and_name():
1715 * parse full path in file into 2 parts: directory and file names
1716 * Should leave final slash (/) at end of dir.
1719 extract_dir_and_name(path
, dir
, name
)
1720 Char
*path
, *dir
, *name
;
1724 p
= Strrchr(path
, '/');
1727 p
= Strrchr(path
, ':');
1728 #endif /* WINNT_NATIVE */
1730 copyn(name
, path
, MAXNAMLEN
);
1735 copyn(name
, p
, MAXNAMLEN
);
1736 copyn(dir
, path
, p
- path
);
1738 } /* end extract_dir_and_name */
1742 * expand "/$old1/$old2/old3/"
1743 * to "/value_of_old1/value_of_old2/old3/"
1753 for (space
= FILSIZ
, p
= new; *old
&& space
> 0;)
1759 if (expdollar(&p
, &old
, &space
, QUOTE
) == NULL
)
1768 * expand ~person/foo to home_directory_of_person/foo
1769 * or =<stack-entry> to <dir in stack entry>
1775 register Char
*o
, *p
;
1779 for (p
= new, o
= &old
[1]; *o
&& *o
!= '/'; *p
++ = *o
++)
1787 /* Special case: if the home directory expands to "/", we do
1788 * not want to create "//" by appending a slash from o.
1790 if (new[0] == '/' && new[1] == '\0' && *o
== '/')
1793 (void) Strcat(new, o
);
1797 if ((p
= globequal(new, old
)) == NULL
) {
1806 (void) Strcpy(new, old
);
1813 * Open the directory given, expanding ~user and $var
1814 * Optionally normalize the path given
1817 expand_dir(dir
, edir
, dfd
, cmd
)
1823 Char tdir
[MAXPATHLEN
+ 1];
1825 if ((dollar(tdir
, dir
) == 0) ||
1826 (tilde(edir
, tdir
) == 0) ||
1827 !(nd
= dnormalize(*edir
? edir
: STRdot
, symlinks
== SYM_IGNORE
||
1828 symlinks
== SYM_EXPAND
)) ||
1829 ((*dfd
= opendir(short2str(nd
))) == NULL
)) {
1831 if (cmd
== SPELL
|| SearchNoDirErr
)
1834 * From: Amos Shapira <amoss@cs.huji.ac.il>
1835 * Print a better message when completion fails
1837 xprintf("\n%S %s\n",
1839 (*tdir
? tdir
: dir
),
1840 (errno
== ENOTDIR
? CGETS(30, 10, "not a directory") :
1841 (errno
== ENOENT
? CGETS(30, 11, "not found") :
1842 CGETS(30, 12, "unreadable"))));
1851 * Copy and append a / if there was one
1853 for (p
= edir
; *p
; p
++)
1856 for (p
= nd
; *p
; p
++)
1861 for (d
= edir
, s
= nd
; (*d
++ = *s
++) != '\0';)
1871 } /* end expand_dir */
1875 * Returns true if the directory should not be stat'd,
1877 * This way, things won't grind to a halt when you complete in /afs
1878 * or very large directories.
1887 if ((vp
= adrof(STRnostat
)) == NULL
|| (cp
= vp
->vec
) == NULL
)
1889 for (; *cp
!= NULL
; cp
++) {
1890 if (Strcmp(*cp
, STRstar
) == 0)
1892 if (Gmatch(dir
, *cp
))
1900 * Return a character that signifies a filetype
1901 * symbology from 4.3 ls command.
1913 * From: veals@crchh84d.bnr.ca (Percy Veals)
1914 * An extra stat is required for HPUX CDF files.
1916 struct stat hpstatb
;
1917 #endif /* S_ISCDF */
1919 if (nostat(dir
)) return(' ');
1921 (void) Strcpy(path
, dir
);
1922 catn(path
, file
, (int) (sizeof(path
) / sizeof(Char
)));
1924 if (lstat(ptr
= short2str(path
), &statb
) != -1)
1925 /* see above #define of lstat */
1928 if (S_ISLNK(statb
.st_mode
)) { /* Symbolic link */
1929 if (adrof(STRlistlinks
)) {
1930 if (stat(ptr
, &statb
) == -1)
1932 else if (S_ISDIR(statb
.st_mode
))
1942 if (S_ISSOCK(statb
.st_mode
)) /* Socket */
1946 if (S_ISFIFO(statb
.st_mode
)) /* Named Pipe */
1950 if (S_ISHIDDEN(statb
.st_mode
)) /* Hidden Directory [aix] */
1954 (void) strcat(ptr
, "+"); /* Must append a '+' and re-stat(). */
1955 if ((stat(ptr
, &hpstatb
) != -1) && S_ISCDF(hpstatb
.st_mode
))
1956 return ('+'); /* Context Dependent Files [hpux] */
1959 if (S_ISNWK(statb
.st_mode
)) /* Network Special [hpux] */
1963 if (S_ISCHR(statb
.st_mode
)) /* char device */
1967 if (S_ISBLK(statb
.st_mode
)) /* block device */
1971 if (S_ISDIR(statb
.st_mode
)) /* normal Directory */
1974 if (statb
.st_mode
& (S_IXUSR
|S_IXGRP
|S_IXOTH
))
1979 } /* end filetype */
1983 * Return trus if the file is a directory
1986 isadirectory(dir
, file
) /* return 1 if dir/file is a directory */
1987 Char
*dir
, *file
; /* uses stat rather than lstat to get dest. */
1990 Char path
[MAXPATHLEN
];
1993 (void) Strcpy(path
, dir
);
1994 catn(path
, file
, (int) (sizeof(path
) / sizeof(Char
)));
1995 if (stat(short2str(path
), &statb
) >= 0) { /* resolve through
1998 if (S_ISSOCK(statb
.st_mode
)) /* Socket */
2002 if (S_ISFIFO(statb
.st_mode
)) /* Named Pipe */
2005 if (S_ISDIR(statb
.st_mode
)) /* normal Directory */
2010 } /* end isadirectory */
2015 * Return how many rows needed to print sorted down columns
2018 find_rows(items
, count
, no_file_suffix
)
2020 int count
, no_file_suffix
;
2022 register int i
, columns
, rows
;
2023 unsigned int maxwidth
= 0;
2025 for (i
= 0; i
< count
; i
++) /* find widest string */
2026 maxwidth
= max(maxwidth
, (unsigned int) Strlen(items
[i
]));
2028 maxwidth
+= no_file_suffix
? 1 : 2; /* for the file tag and space */
2029 columns
= (TermH
+ 1) / maxwidth
; /* PWP: terminal size change */
2032 rows
= (count
+ (columns
- 1)) / columns
;
2035 } /* end rows_needed_by_print_by_column */
2038 /* print_by_column():
2039 * Print sorted down columns or across columns when the first
2040 * word of $listflags shell variable contains 'x'.
2044 print_by_column(dir
, items
, count
, no_file_suffix
)
2045 register Char
*dir
, *items
[];
2046 int count
, no_file_suffix
;
2048 register int i
, r
, c
, columns
, rows
;
2049 unsigned int w
, maxwidth
= 0;
2053 lbuffed
= 0; /* turn off line buffering */
2056 across
= ((val
= varval(STRlistflags
)) != STRNULL
) &&
2057 (Strchr(val
, 'x') != NULL
);
2059 for (i
= 0; i
< count
; i
++) /* find widest string */
2060 maxwidth
= max(maxwidth
, (unsigned int) Strlen(items
[i
]));
2062 maxwidth
+= no_file_suffix
? 1 : 2; /* for the file tag and space */
2063 columns
= TermH
/ maxwidth
; /* PWP: terminal size change */
2064 if (!columns
|| !isatty(didfds
? 1 : SHOUT
))
2066 rows
= (count
+ (columns
- 1)) / columns
;
2069 for (r
= 0; r
< rows
; r
++) {
2070 for (c
= 0; c
< columns
; c
++) {
2071 i
= across
? (i
+ 1) : (c
* rows
+ r
);
2074 w
= (unsigned int) Strlen(items
[i
]);
2077 if (no_file_suffix
) {
2078 /* Print the command name */
2079 Char f
= items
[i
][w
- 1];
2080 items
[i
][w
- 1] = 0;
2081 print_with_color(items
[i
], w
- 1, f
);
2084 /* Print filename followed by '/' or '*' or ' ' */
2085 print_with_color(items
[i
], w
, filetype(dir
, items
[i
]));
2088 #else /* ifndef COLOR_LS_F */
2089 if (no_file_suffix
) {
2090 /* Print the command name */
2091 xprintf("%S", items
[i
]);
2094 /* Print filename followed by '/' or '*' or ' ' */
2095 xprintf("%S%c", items
[i
],
2096 filetype(dir
, items
[i
]));
2099 #endif /* COLOR_LS_F */
2101 if (c
< (columns
- 1)) /* Not last column? */
2102 for (; w
< maxwidth
; w
++)
2113 lbuffed
= 1; /* turn back on line buffering */
2115 } /* end print_by_column */
2119 * Compare strings ignoring the quoting chars
2123 register Char
*str1
, *str2
;
2125 for (; *str1
&& samecase(*str1
& TRIM
) == samecase(*str2
& TRIM
);
2129 * The following case analysis is necessary so that characters which look
2130 * negative collate low against normal characters but high against the
2131 * end-of-string NUL.
2133 if (*str1
== '\0' && *str2
== '\0')
2135 else if (*str1
== '\0')
2137 else if (*str2
== '\0')
2140 return ((*str1
& TRIM
) - (*str2
& TRIM
));
2145 * Comparison routine for qsort
2148 fcompare(file1
, file2
)
2149 Char
**file1
, **file2
;
2151 return (int) collate(*file1
, *file2
);
2152 } /* end fcompare */
2156 * Concatenate src onto tail of des.
2157 * Des is a string whose maximum length is count.
2158 * Always null terminate.
2161 catn(des
, src
, count
)
2162 register Char
*des
, *src
;
2165 while (--count
>= 0 && *des
)
2167 while (--count
>= 0)
2168 if ((*des
++ = *src
++) == 0)
2175 * like strncpy but always leave room for trailing \0
2176 * and always null terminate.
2179 copyn(des
, src
, count
)
2180 register Char
*des
, *src
;
2183 while (--count
>= 0)
2184 if ((*des
++ = *src
++) == 0)
2191 * like it's normal string counter-part
2192 * [apollo uses that in tc.os.c, so it cannot be static]
2201 len
= (int) Strlen(str
);
2202 /* Search the STR_environ for the entry matching str. */
2203 for (var
= STR_environ
; var
!= NULL
&& *var
!= NULL
; var
++)
2204 if (Strlen(*var
) >= len
&& (*var
)[len
] == '=') {
2205 /* Temporarily terminate the string so we can copy the variable
2208 res
= StrQcmp(*var
, str
);
2209 /* Restore the '=' and return a pointer to the value of the
2210 environment variable. */
2213 return (&((*var
)[len
+ 1]));
2219 struct scroll_tab_list
*scroll_tab
= 0;
2222 add_scroll_tab(item
)
2225 struct scroll_tab_list
*new_scroll
;
2227 new_scroll
= (struct scroll_tab_list
*) xmalloc((size_t)
2228 sizeof(struct scroll_tab_list
));
2229 new_scroll
->element
= Strsave(item
);
2230 new_scroll
->next
= scroll_tab
;
2231 scroll_tab
= new_scroll
;
2235 choose_scroll_tab(exp_name
, cnt
)
2239 struct scroll_tab_list
*loop
;
2243 ptr
= (Char
**) xmalloc((size_t) sizeof(Char
*) * cnt
);
2245 for(loop
= scroll_tab
; loop
&& (tmp
>= 0); loop
= loop
->next
)
2246 ptr
[--tmp
] = loop
->element
;
2248 qsort((ptr_t
) ptr
, (size_t) cnt
, sizeof(Char
*),
2249 (int (*) __P((const void *, const void *))) fcompare
);
2251 copyn(*exp_name
, ptr
[curchoice
], (int) Strlen(ptr
[curchoice
]));
2258 struct scroll_tab_list
*loop
;
2262 scroll_tab
= scroll_tab
->next
;
2263 xfree((ptr_t
) loop
->element
);
2264 xfree((ptr_t
) loop
);