source: mainline/uspace/app/bdsh/compl.c@ 9be9c4d

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 9be9c4d was 9be9c4d, checked in by Jiri Svoboda <jiri@…>, 14 years ago

Shell command and file-name completion.

  • Property mode set to 100644
File size: 8.0 KB
Line 
1/*
2 * Copyright (c) 2011 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <bool.h>
30#include <dirent.h>
31#include <errno.h>
32#include <macros.h>
33#include <stdlib.h>
34#include <sys/stat.h>
35
36#include "cmds/cmds.h"
37#include "compl.h"
38#include "exec.h"
39
40static int compl_init(wchar_t *text, size_t pos, size_t *cstart, void **state);
41static int compl_get_next(void *state, char **compl);
42static void compl_fini(void *state);
43
44/** Bdsh implementation of completion ops. */
45tinput_compl_ops_t compl_ops = {
46 .init = compl_init,
47 .get_next = compl_get_next,
48 .fini = compl_fini
49};
50
51/** Completion state object.
52 *
53 * The state object contains 'iterators' for modules, builtins and
54 * executables in directories.
55 */
56typedef struct {
57 /** String prefix which we are trying to complete. */
58 char *prefix;
59 /** Length of string prefix (number of characters) */
60 size_t prefix_len;
61
62 /** Pointer inside list of modules */
63 module_t *module;
64 /** Pointer inside list of builtins */
65 builtin_t *builtin;
66
67 /** Pointer inside list of directories */
68 const char **path;
69 /** Current open directory */
70 DIR *dir;
71
72 char *last_compl;
73
74 /**
75 * @c true if we are completing a command, @c false if we are
76 * completing an argument
77 */
78 bool is_command;
79} compl_t;
80
81/** Init completion.
82 *
83 * Set up iterators in completion object, based on current token.
84 */
85static int compl_init(wchar_t *text, size_t pos, size_t *cstart, void **state)
86{
87 compl_t *cs = NULL;
88 size_t p;
89 size_t pref_size;
90 char *stext = NULL;
91 char *prefix = NULL;
92 char *dirname = NULL;
93 char *rpath_sep;
94 static const char *dirlist_arg[] = { ".", NULL };
95 int retval;
96
97 cs = calloc(1, sizeof(compl_t));
98 if (!cs) {
99 retval = ENOMEM;
100 goto error;
101 }
102
103 /*
104 * Copy token pointed to by caret from start up to the caret.
105 * XXX Ideally we would use the standard tokenizer.
106 */
107 p = pos;
108 while (p > 0 && text[p - 1] != (wchar_t) ' ')
109 --p;
110 *cstart = p;
111
112 /* Convert text buffer to string */
113 stext = wstr_to_astr(text + *cstart);
114 if (stext == NULL) {
115 retval = ENOMEM;
116 goto error;
117 }
118
119 /* Extract the prefix being completed */
120 pref_size = str_lsize(stext, pos - *cstart);
121 prefix = malloc(pref_size + 1);
122 if (prefix == NULL) {
123 retval = ENOMEM;
124 goto error;
125 }
126
127 str_ncpy(prefix, pref_size + 1, stext, pref_size);
128
129 /*
130 * Determine if the token being completed is a command or argument.
131 * We look at the previous token. If there is none or it is a pipe
132 * ('|'), it is a command, otherwise it is an argument.
133 * XXX Again we should use the standard tokenizer/parser.
134 */
135
136 /* Skip any whitespace before current token */
137 while (p > 0 && text[p - 1] == (wchar_t) ' ')
138 --p;
139
140 /*
141 * It is a command if it is the first token or if it immediately
142 * follows a pipe token.
143 */
144 if (p == 0 || text[p - 1] == '|')
145 cs->is_command = true;
146 else
147 cs->is_command = false;
148
149 rpath_sep = str_rchr(prefix, '/');
150 if (rpath_sep != NULL) {
151 /* Extract path. For path beginning with '/' keep the '/'. */
152 dirname = str_ndup(prefix, max(1, rpath_sep - prefix));
153 if (dirname == NULL) {
154 retval = ENOMEM;
155 goto error;
156 }
157
158 /* Extract name prefix */
159 cs->prefix = str_dup(rpath_sep + 1);
160 if (cs->prefix == NULL) {
161 retval = ENOMEM;
162 goto error;
163 }
164 *cstart += rpath_sep + 1 - prefix;
165// printf("cstart=%zu\n", *cstart);
166 free(prefix);
167
168// printf("\ncs->prefix='%s', dirname='%s'\n", cs->prefix, dirname);
169
170 static const char *dlist2[2];
171 dlist2[0] = dirname;
172 dlist2[1] = NULL;
173 cs->path = dlist2;
174 /* XXX dirname won't be freed */
175 } else if (cs->is_command) {
176 /* Command without path */
177 cs->module = modules;
178 cs->builtin = builtins;
179 cs->prefix = prefix;
180 cs->path = &search_dir[0];
181 } else {
182 /* Argument without path */
183 cs->prefix = prefix;
184 cs->path = &dirlist_arg[0];
185 }
186
187 cs->prefix_len = str_length(cs->prefix);
188
189 *state = cs;
190 return EOK;
191
192error:
193 /* Error cleanup */
194
195 if (cs != NULL && cs->prefix != NULL)
196 free(cs->prefix);
197 if (dirname != NULL)
198 free(dirname);
199 if (prefix != NULL)
200 free(prefix);
201 if (stext != NULL)
202 free(stext);
203 if (cs != NULL)
204 free(cs);
205
206 return retval;
207}
208
209/** Determine if completion matches the required prefix.
210 *
211 * Required prefix is stored in @a cs->prefix.
212 *
213 * @param cs Completion state object
214 * @param compl Completion string
215 * @return @c true when @a compl matches, @c false otherwise
216 */
217static bool compl_match_prefix(compl_t *cs, const char *compl)
218{
219 return str_lcmp(compl, cs->prefix, cs->prefix_len) == 0;
220}
221
222/** Get next match. */
223static int compl_get_next(void *state, char **compl)
224{
225 compl_t *cs = (compl_t *) state;
226 struct dirent *dent;
227
228 *compl = NULL;
229
230 if (cs->last_compl != NULL) {
231 free(cs->last_compl);
232 cs->last_compl = NULL;
233 }
234
235 /* Modules */
236 if (cs->module != NULL) {
237 while (*compl == NULL && cs->module->name != NULL) {
238 if (compl_match_prefix(cs, cs->module->name)) {
239 asprintf(compl, "%s ", cs->module->name);
240 cs->last_compl = *compl;
241 if (*compl == NULL)
242 return ENOMEM;
243 }
244 cs->module++;
245 }
246 }
247
248 /* Builtins */
249 if (cs->builtin != NULL) {
250 while (*compl == NULL && cs->builtin->name != NULL) {
251 if (compl_match_prefix(cs, cs->builtin->name)) {
252 asprintf(compl, "%s ", cs->builtin->name);
253 cs->last_compl = *compl;
254 if (*compl == NULL)
255 return ENOMEM;
256 }
257 cs->builtin++;
258 }
259 }
260
261 /* Files and directories. We scan entries from a set of directories. */
262 if (cs->path != NULL) {
263 while (*compl == NULL) {
264 /* Open next directory */
265 while (cs->dir == NULL) {
266 if (*cs->path == NULL)
267 break;
268
269 cs->dir = opendir(*cs->path);
270 }
271
272 /* If it was the last one, we are done */
273 if (cs->dir == NULL) {
274 cs->path = NULL;
275 break;
276 }
277
278 /* Read next directory entry */
279 dent = readdir(cs->dir);
280 if (dent == NULL) {
281 /* Error. Close directory, go to next one */
282 closedir(cs->dir);
283 cs->dir = NULL;
284 cs->path++;
285 continue;
286 }
287
288 if (compl_match_prefix(cs, dent->d_name)) {
289 /* Construct pathname */
290 char *ent_path;
291 asprintf(&ent_path, "%s/%s", *cs->path, dent->d_name);
292 struct stat ent_stat;
293 if (stat(ent_path, &ent_stat) != EOK) {
294 /* Error */
295 free(ent_path);
296 continue;
297 }
298
299 free(ent_path);
300
301 /* If completing command, do not match directories. */
302 if (!ent_stat.is_directory || !cs->is_command) {
303 asprintf(compl, "%s%c", dent->d_name,
304 ent_stat.is_directory ? '/' : ' ');
305 cs->last_compl = *compl;
306 if (*compl == NULL)
307 return ENOMEM;
308 }
309 }
310 }
311 }
312
313 if (*compl == NULL)
314 return ENOENT;
315
316 return EOK;
317}
318
319/** Finish completion operation. */
320static void compl_fini(void *state)
321{
322 compl_t *cs = (compl_t *) state;
323
324 if (cs->last_compl != NULL)
325 free(cs->last_compl);
326 if (cs->dir != NULL)
327 closedir(cs->dir);
328
329 free(cs->prefix);
330 free(cs);
331}
Note: See TracBrowser for help on using the repository browser.