complete.c revision 1.32 1 /* $NetBSD: complete.c,v 1.32 1999/09/28 06:47:40 lukem Exp $ */
2
3 /*-
4 * Copyright (c) 1997, 1998, 1999 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Luke Mewburn.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the NetBSD
21 * Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 * contributors may be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 #include <sys/cdefs.h>
40 #ifndef lint
41 __RCSID("$NetBSD: complete.c,v 1.32 1999/09/28 06:47:40 lukem Exp $");
42 #endif /* not lint */
43
44 /*
45 * FTP user program - command and file completion routines
46 */
47
48 #include <sys/stat.h>
49
50 #include <ctype.h>
51 #include <err.h>
52 #include <dirent.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56
57 #include "ftp_var.h"
58
59 #ifndef NO_EDITCOMPLETE
60
61 static int comparstr __P((const void *, const void *));
62 static unsigned char complete_ambiguous __P((char *, int, StringList *));
63 static unsigned char complete_command __P((char *, int));
64 static unsigned char complete_local __P((char *, int));
65 static unsigned char complete_remote __P((char *, int));
66
67 static int
68 comparstr(a, b)
69 const void *a, *b;
70 {
71 return (strcmp(*(const char **)a, *(const char **)b));
72 }
73
74 /*
75 * Determine if complete is ambiguous. If unique, insert.
76 * If no choices, error. If unambiguous prefix, insert that.
77 * Otherwise, list choices. words is assumed to be filtered
78 * to only contain possible choices.
79 * Args:
80 * word word which started the match
81 * list list by default
82 * words stringlist containing possible matches
83 * Returns a result as per el_set(EL_ADDFN, ...)
84 */
85 static unsigned char
86 complete_ambiguous(word, list, words)
87 char *word;
88 int list;
89 StringList *words;
90 {
91 char insertstr[MAXPATHLEN];
92 char *lastmatch;
93 int i, j;
94 size_t matchlen, wordlen;
95
96 wordlen = strlen(word);
97 if (words->sl_cur == 0)
98 return (CC_ERROR); /* no choices available */
99
100 if (words->sl_cur == 1) { /* only once choice available */
101 char *p = words->sl_str[0] + wordlen;
102 ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
103 if (el_insertstr(el, insertstr) == -1)
104 return (CC_ERROR);
105 else
106 return (CC_REFRESH);
107 }
108
109 if (!list) {
110 matchlen = 0;
111 lastmatch = words->sl_str[0];
112 matchlen = strlen(lastmatch);
113 for (i = 1 ; i < words->sl_cur ; i++) {
114 for (j = wordlen ; j < strlen(words->sl_str[i]); j++)
115 if (lastmatch[j] != words->sl_str[i][j])
116 break;
117 if (j < matchlen)
118 matchlen = j;
119 }
120 if (matchlen > wordlen) {
121 ftpvis(insertstr, sizeof(insertstr),
122 lastmatch + wordlen, matchlen - wordlen);
123 if (el_insertstr(el, insertstr) == -1)
124 return (CC_ERROR);
125 else
126 return (CC_REFRESH_BEEP);
127 }
128 }
129
130 putc('\n', ttyout);
131 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
132 list_vertical(words);
133 return (CC_REDISPLAY);
134 }
135
136 /*
137 * Complete a command
138 */
139 static unsigned char
140 complete_command(word, list)
141 char *word;
142 int list;
143 {
144 struct cmd *c;
145 StringList *words;
146 size_t wordlen;
147 unsigned char rv;
148
149 words = sl_init();
150 wordlen = strlen(word);
151
152 for (c = cmdtab; c->c_name != NULL; c++) {
153 if (wordlen > strlen(c->c_name))
154 continue;
155 if (strncmp(word, c->c_name, wordlen) == 0)
156 sl_add(words, c->c_name);
157 }
158
159 rv = complete_ambiguous(word, list, words);
160 if (rv == CC_REFRESH) {
161 if (el_insertstr(el, " ") == -1)
162 rv = CC_ERROR;
163 }
164 sl_free(words, 0);
165 return (rv);
166 }
167
168 /*
169 * Complete a local file
170 */
171 static unsigned char
172 complete_local(word, list)
173 char *word;
174 int list;
175 {
176 StringList *words;
177 char dir[MAXPATHLEN];
178 char *file;
179 DIR *dd;
180 struct dirent *dp;
181 unsigned char rv;
182 size_t len;
183
184 if ((file = strrchr(word, '/')) == NULL) {
185 dir[0] = '.';
186 dir[1] = '\0';
187 file = word;
188 } else {
189 if (file == word) {
190 dir[0] = '/';
191 dir[1] = '\0';
192 } else
193 (void)strlcpy(dir, word, file - word + 1);
194 file++;
195 }
196 if (dir[0] == '~') {
197 char *p;
198
199 if ((p = globulize(dir)) == NULL)
200 return (CC_ERROR);
201 (void)strlcpy(dir, p, sizeof(dir));
202 free(p);
203 }
204
205 if ((dd = opendir(dir)) == NULL)
206 return (CC_ERROR);
207
208 words = sl_init();
209
210 len = strlen(file);
211
212 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
213 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
214 continue;
215
216 #if defined(DIRENT_MISSING_D_NAMLEN)
217 if (len > strlen(dp->d_name))
218 continue;
219 #else
220 if (len > dp->d_namlen)
221 continue;
222 #endif
223 if (strncmp(file, dp->d_name, len) == 0) {
224 char *tcp;
225
226 tcp = xstrdup(dp->d_name);
227 sl_add(words, tcp);
228 }
229 }
230 closedir(dd);
231
232 rv = complete_ambiguous(file, list, words);
233 if (rv == CC_REFRESH) {
234 struct stat sb;
235 char path[MAXPATHLEN];
236
237 (void)strlcpy(path, dir, sizeof(path));
238 (void)strlcat(path, "/", sizeof(path));
239 (void)strlcat(path, words->sl_str[0], sizeof(path));
240
241 if (stat(path, &sb) >= 0) {
242 char suffix[2] = " ";
243
244 if (S_ISDIR(sb.st_mode))
245 suffix[0] = '/';
246 if (el_insertstr(el, suffix) == -1)
247 rv = CC_ERROR;
248 }
249 }
250 sl_free(words, 1);
251 return (rv);
252 }
253
254 /*
255 * Complete a remote file
256 */
257 static unsigned char
258 complete_remote(word, list)
259 char *word;
260 int list;
261 {
262 static StringList *dirlist;
263 static char lastdir[MAXPATHLEN];
264 StringList *words;
265 char dir[MAXPATHLEN];
266 char *file, *cp;
267 int i;
268 unsigned char rv;
269
270 char *dummyargv[] = { "complete", NULL, NULL };
271 dummyargv[1] = dir;
272
273 if ((file = strrchr(word, '/')) == NULL) {
274 dir[0] = '.';
275 dir[1] = '\0';
276 file = word;
277 } else {
278 cp = file;
279 while (*cp == '/' && cp > word)
280 cp--;
281 (void)strlcpy(dir, word, cp - word + 2);
282 file++;
283 }
284
285 if (dirchange || strcmp(dir, lastdir) != 0) { /* dir not cached */
286 char *emesg;
287
288 if (dirlist != NULL)
289 sl_free(dirlist, 1);
290 dirlist = sl_init();
291
292 mflag = 1;
293 emesg = NULL;
294 while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) {
295 char *tcp;
296
297 if (!mflag)
298 continue;
299 if (*cp == '\0') {
300 mflag = 0;
301 continue;
302 }
303 tcp = strrchr(cp, '/');
304 if (tcp)
305 tcp++;
306 else
307 tcp = cp;
308 tcp = xstrdup(tcp);
309 sl_add(dirlist, tcp);
310 }
311 if (emesg != NULL) {
312 fprintf(ttyout, "\n%s\n", emesg);
313 return (CC_REDISPLAY);
314 }
315 (void)strlcpy(lastdir, dir, sizeof(lastdir));
316 dirchange = 0;
317 }
318
319 words = sl_init();
320 for (i = 0; i < dirlist->sl_cur; i++) {
321 cp = dirlist->sl_str[i];
322 if (strlen(file) > strlen(cp))
323 continue;
324 if (strncmp(file, cp, strlen(file)) == 0)
325 sl_add(words, cp);
326 }
327 rv = complete_ambiguous(file, list, words);
328 sl_free(words, 0);
329 return (rv);
330 }
331
332 /*
333 * Generic complete routine
334 */
335 unsigned char
336 complete(el, ch)
337 EditLine *el;
338 int ch;
339 {
340 static char word[FTPBUFLEN];
341 static int lastc_argc, lastc_argo;
342
343 struct cmd *c;
344 const LineInfo *lf;
345 int celems, dolist;
346 size_t len;
347
348 lf = el_line(el);
349 len = lf->lastchar - lf->buffer;
350 if (len >= sizeof(line))
351 return (CC_ERROR);
352 (void)strlcpy(line, lf->buffer, len + 1);
353 cursor_pos = line + (lf->cursor - lf->buffer);
354 lastc_argc = cursor_argc; /* remember last cursor pos */
355 lastc_argo = cursor_argo;
356 makeargv(); /* build argc/argv of current line */
357
358 if (cursor_argo >= sizeof(word))
359 return (CC_ERROR);
360
361 dolist = 0;
362 /* if cursor and word is same, list alternatives */
363 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
364 && strncmp(word, margv[cursor_argc], cursor_argo) == 0)
365 dolist = 1;
366 else if (cursor_argc < margc)
367 (void)strlcpy(word, margv[cursor_argc], cursor_argo + 1);
368 word[cursor_argo] = '\0';
369
370 if (cursor_argc == 0)
371 return (complete_command(word, dolist));
372
373 c = getcmd(margv[0]);
374 if (c == (struct cmd *)-1 || c == 0)
375 return (CC_ERROR);
376 celems = strlen(c->c_complete);
377
378 /* check for 'continuation' completes (which are uppercase) */
379 if ((cursor_argc > celems) && (celems > 0)
380 && isupper((unsigned char) c->c_complete[celems-1]))
381 cursor_argc = celems;
382
383 if (cursor_argc > celems)
384 return (CC_ERROR);
385
386 switch (c->c_complete[cursor_argc - 1]) {
387 case 'l': /* local complete */
388 case 'L':
389 return (complete_local(word, dolist));
390 case 'r': /* remote complete */
391 case 'R':
392 if (connected != -1) {
393 fputs("\nMust be logged in to complete.\n",
394 ttyout);
395 return (CC_REDISPLAY);
396 }
397 return (complete_remote(word, dolist));
398 case 'c': /* command complete */
399 case 'C':
400 return (complete_command(word, dolist));
401 case 'n': /* no complete */
402 default:
403 return (CC_ERROR);
404 }
405 /* NOTREACHED */
406 }
407
408 #endif /* !NO_EDITCOMPLETE */
409