source: mainline/uspace/app/bdsh/compl.c@ c916dfc

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since c916dfc was e14a103, checked in by Martin Sucha <sucha14@…>, 14 years ago

Use tokenizer in bdsh completion

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