complete.c revision 1.16 1 /* $NetBSD: complete.c,v 1.16 1998/08/08 07:51:30 lukem Exp $ */
2
3 /*-
4 * Copyright (c) 1997 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 #ifndef SMALL
40
41 #include <sys/cdefs.h>
42 #ifndef lint
43 __RCSID("$NetBSD: complete.c,v 1.16 1998/08/08 07:51:30 lukem Exp $");
44 #endif /* not lint */
45
46 /*
47 * FTP user program - command and file completion routines
48 */
49
50 #include <sys/stat.h>
51
52 #include <ctype.h>
53 #include <err.h>
54 #include <dirent.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58
59 #include "ftp_var.h"
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(*(char **)a, *(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 (void)strcpy(insertstr, words->sl_str[0]);
102 if (el_insertstr(el, insertstr + wordlen) == -1)
103 return (CC_ERROR);
104 else
105 return (CC_REFRESH);
106 }
107
108 if (!list) {
109 matchlen = 0;
110 lastmatch = words->sl_str[0];
111 matchlen = strlen(lastmatch);
112 for (i = 1 ; i < words->sl_cur ; i++) {
113 for (j = wordlen ; j < strlen(words->sl_str[i]); j++)
114 if (lastmatch[j] != words->sl_str[i][j])
115 break;
116 if (j < matchlen)
117 matchlen = j;
118 }
119 if (matchlen > wordlen) {
120 (void)strncpy(insertstr, lastmatch, matchlen);
121 insertstr[matchlen] = '\0';
122 if (el_insertstr(el, insertstr + wordlen) == -1)
123 return (CC_ERROR);
124 else
125 return (CC_REFRESH_BEEP);
126 }
127 }
128
129 putc('\n', ttyout);
130 qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
131 list_vertical(words);
132 return (CC_REDISPLAY);
133 }
134
135 /*
136 * Complete a command
137 */
138 static unsigned char
139 complete_command(word, list)
140 char *word;
141 int list;
142 {
143 struct cmd *c;
144 StringList *words;
145 size_t wordlen;
146 unsigned char rv;
147
148 words = sl_init();
149 wordlen = strlen(word);
150
151 for (c = cmdtab; c->c_name != NULL; c++) {
152 if (wordlen > strlen(c->c_name))
153 continue;
154 if (strncmp(word, c->c_name, wordlen) == 0)
155 sl_add(words, c->c_name);
156 }
157
158 rv = complete_ambiguous(word, list, words);
159 if (rv == CC_REFRESH) {
160 if (el_insertstr(el, " ") == -1)
161 rv = CC_ERROR;
162 }
163 sl_free(words, 0);
164 return (rv);
165 }
166
167 /*
168 * Complete a local file
169 */
170 static unsigned char
171 complete_local(word, list)
172 char *word;
173 int list;
174 {
175 StringList *words;
176 char dir[MAXPATHLEN];
177 char *file;
178 DIR *dd;
179 struct dirent *dp;
180 unsigned char rv;
181 size_t len;
182
183 if ((file = strrchr(word, '/')) == NULL) {
184 dir[0] = '.';
185 dir[1] = '\0';
186 file = word;
187 } else {
188 if (file == word) {
189 dir[0] = '/';
190 dir[1] = '\0';
191 } else {
192 (void)strncpy(dir, word, file - word);
193 dir[file - word] = '\0';
194 }
195 file++;
196 }
197
198 if ((dd = opendir(dir)) == NULL)
199 return (CC_ERROR);
200
201 words = sl_init();
202
203 len = strlen(file);
204
205 for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
206 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
207 continue;
208
209 #ifndef __SVR4
210 if (len > dp->d_namlen)
211 continue;
212 #else
213 if (len > strlen(dp->d_name))
214 continue;
215 #endif
216 if (strncmp(file, dp->d_name, len) == 0) {
217 char *tcp;
218
219 tcp = xstrdup(dp->d_name);
220 sl_add(words, tcp);
221 }
222 }
223 closedir(dd);
224
225 rv = complete_ambiguous(file, list, words);
226 if (rv == CC_REFRESH) {
227 struct stat sb;
228 char path[MAXPATHLEN];
229
230 snprintf(path, sizeof(path), "%s/%s", dir, words->sl_str[0]);
231 if (stat(path, &sb) >= 0) {
232 char suffix[2] = " ";
233
234 if (S_ISDIR(sb.st_mode))
235 suffix[0] = '/';
236 if (el_insertstr(el, suffix) == -1)
237 rv = CC_ERROR;
238 }
239 }
240 sl_free(words, 1);
241 return (rv);
242 }
243
244 /*
245 * Complete a remote file
246 */
247 static unsigned char
248 complete_remote(word, list)
249 char *word;
250 int list;
251 {
252 static StringList *dirlist;
253 static char lastdir[MAXPATHLEN];
254 StringList *words;
255 char dir[MAXPATHLEN];
256 char *file, *cp;
257 int i;
258 unsigned char rv;
259
260 char *dummyargv[] = { "complete", NULL, NULL };
261 dummyargv[1] = dir;
262
263 if ((file = strrchr(word, '/')) == NULL) {
264 dir[0] = '.';
265 dir[1] = '\0';
266 file = word;
267 } else {
268 cp = file;
269 while (*cp == '/' && cp > word)
270 cp--;
271 (void)strncpy(dir, word, cp - word + 1);
272 dir[cp - word + 1] = '\0';
273 file++;
274 }
275
276 if (dirchange || strcmp(dir, lastdir) != 0) { /* dir not cached */
277 char *emesg;
278
279 if (dirlist != NULL)
280 sl_free(dirlist, 1);
281 dirlist = sl_init();
282
283 mflag = 1;
284 emesg = NULL;
285 while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) {
286 char *tcp;
287
288 if (!mflag)
289 continue;
290 if (*cp == '\0') {
291 mflag = 0;
292 continue;
293 }
294 tcp = strrchr(cp, '/');
295 if (tcp)
296 tcp++;
297 else
298 tcp = cp;
299 tcp = xstrdup(tcp);
300 sl_add(dirlist, tcp);
301 }
302 if (emesg != NULL) {
303 fprintf(ttyout, "\n%s\n", emesg);
304 return (CC_REDISPLAY);
305 }
306 (void)strcpy(lastdir, dir);
307 dirchange = 0;
308 }
309
310 words = sl_init();
311 for (i = 0; i < dirlist->sl_cur; i++) {
312 cp = dirlist->sl_str[i];
313 if (strlen(file) > strlen(cp))
314 continue;
315 if (strncmp(file, cp, strlen(file)) == 0)
316 sl_add(words, cp);
317 }
318 rv = complete_ambiguous(file, list, words);
319 sl_free(words, 0);
320 return (rv);
321 }
322
323 /*
324 * Generic complete routine
325 */
326 unsigned char
327 complete(el, ch)
328 EditLine *el;
329 int ch;
330 {
331 static char word[FTPBUFLEN];
332 static int lastc_argc, lastc_argo;
333
334 struct cmd *c;
335 const LineInfo *lf;
336 int celems, dolist;
337 size_t len;
338
339 lf = el_line(el);
340 len = lf->lastchar - lf->buffer;
341 if (len >= sizeof(line))
342 return (CC_ERROR);
343 (void)strncpy(line, lf->buffer, len);
344 line[len] = '\0';
345 cursor_pos = line + (lf->cursor - lf->buffer);
346 lastc_argc = cursor_argc; /* remember last cursor pos */
347 lastc_argo = cursor_argo;
348 makeargv(); /* build argc/argv of current line */
349
350 if (cursor_argo >= sizeof(word))
351 return (CC_ERROR);
352
353 dolist = 0;
354 /* if cursor and word is same, list alternatives */
355 if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
356 && strncmp(word, margv[cursor_argc], cursor_argo) == 0)
357 dolist = 1;
358 else
359 (void)strncpy(word, margv[cursor_argc], cursor_argo);
360 word[cursor_argo] = '\0';
361
362 if (cursor_argc == 0)
363 return (complete_command(word, dolist));
364
365 c = getcmd(margv[0]);
366 if (c == (struct cmd *)-1 || c == 0)
367 return (CC_ERROR);
368 celems = strlen(c->c_complete);
369
370 /* check for 'continuation' completes (which are uppercase) */
371 if ((cursor_argc > celems) && (celems > 0)
372 && isupper((unsigned char) c->c_complete[celems-1]))
373 cursor_argc = celems;
374
375 if (cursor_argc > celems)
376 return (CC_ERROR);
377
378 switch (c->c_complete[cursor_argc - 1]) {
379 case 'l': /* local complete */
380 case 'L':
381 return (complete_local(word, dolist));
382 case 'r': /* remote complete */
383 case 'R':
384 if (connected != -1) {
385 fputs("\nMust be logged in to complete.\n",
386 ttyout);
387 return (CC_REDISPLAY);
388 }
389 return (complete_remote(word, dolist));
390 case 'c': /* command complete */
391 case 'C':
392 return (complete_command(word, dolist));
393 case 'n': /* no complete */
394 default:
395 return (CC_ERROR);
396 }
397
398 return (CC_ERROR);
399 }
400
401 #endif /* !SMALL */
402