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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since ac2caecb was ac2caecb, checked in by Matthieu Riolo <matthieu.riolo@…>, 7 years ago

adding alias to completion

  • Property mode set to 100644
File size: 10.6 KB
RevLine 
[9be9c4d]1/*
2 * Copyright (c) 2011 Jiri Svoboda
[e14a103]3 * Copyright (c) 2011 Martin Sucha
[9be9c4d]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
[3e6a98c5]30#include <stdbool.h>
[9be9c4d]31#include <dirent.h>
32#include <errno.h>
33#include <macros.h>
34#include <stdlib.h>
[23a0368]35#include <vfs/vfs.h>
[1d6dd2a]36#include <str.h>
[ac2caecb]37#include <adt/odict.h>
[9be9c4d]38
[ac2caecb]39#include "scli.h"
[9be9c4d]40#include "cmds/cmds.h"
41#include "compl.h"
42#include "exec.h"
[e14a103]43#include "tok.h"
[9be9c4d]44
[b7fd2a0]45static errno_t compl_init(wchar_t *text, size_t pos, size_t *cstart, void **state);
46static errno_t compl_get_next(void *state, char **compl);
[9be9c4d]47static void compl_fini(void *state);
48
49/** Bdsh implementation of completion ops. */
50tinput_compl_ops_t compl_ops = {
51 .init = compl_init,
52 .get_next = compl_get_next,
53 .fini = compl_fini
54};
55
56/** Completion state object.
57 *
58 * The state object contains 'iterators' for modules, builtins and
59 * executables in directories.
60 */
61typedef struct {
62 /** String prefix which we are trying to complete. */
63 char *prefix;
64 /** Length of string prefix (number of characters) */
65 size_t prefix_len;
66
[ac2caecb]67 /* Pointer to the current alias */
68 odlink_t *alias_link;
69
[9be9c4d]70 /** Pointer inside list of modules */
71 module_t *module;
72 /** Pointer inside list of builtins */
73 builtin_t *builtin;
74
75 /** Pointer inside list of directories */
[33b8d024]76 const char *const *path;
[3e5c48c9]77 /** If not @c NULL, should be freed in the end. */
[33b8d024]78 char **path_list;
[9be9c4d]79 /** Current open directory */
80 DIR *dir;
81
82 char *last_compl;
83
84 /**
85 * @c true if we are completing a command, @c false if we are
86 * completing an argument
87 */
88 bool is_command;
89} compl_t;
90
91/** Init completion.
92 *
93 * Set up iterators in completion object, based on current token.
94 */
[b7fd2a0]95static errno_t compl_init(wchar_t *text, size_t pos, size_t *cstart, void **state)
[9be9c4d]96{
97 compl_t *cs = NULL;
98 char *stext = NULL;
99 char *prefix = NULL;
100 char *dirname = NULL;
[b7fd2a0]101 errno_t retval;
[a35b458]102
[8a6ba94]103 token_t *tokens = calloc(WORD_MAX, sizeof(token_t));
104 if (tokens == NULL) {
105 retval = ENOMEM;
106 goto error;
107 }
[a35b458]108
[5935c079]109 size_t pref_size;
110 char *rpath_sep;
111 static const char *dirlist_arg[] = { ".", NULL };
112 tokenizer_t tok;
113 ssize_t current_token;
[e14a103]114 size_t tokens_length;
[a35b458]115
[9be9c4d]116 cs = calloc(1, sizeof(compl_t));
117 if (!cs) {
118 retval = ENOMEM;
119 goto error;
120 }
[a35b458]121
[9be9c4d]122 /* Convert text buffer to string */
[e14a103]123 stext = wstr_to_astr(text);
[9be9c4d]124 if (stext == NULL) {
125 retval = ENOMEM;
126 goto error;
127 }
[a35b458]128
[e14a103]129 /* Tokenize the input string */
130 retval = tok_init(&tok, stext, tokens, WORD_MAX);
131 if (retval != EOK) {
132 goto error;
133 }
[a35b458]134
[e14a103]135 retval = tok_tokenize(&tok, &tokens_length);
136 if (retval != EOK) {
137 goto error;
138 }
[a35b458]139
[e14a103]140 /* Find the current token */
[5935c079]141 for (current_token = 0; current_token < (ssize_t) tokens_length;
[f737c1d5]142 current_token++) {
[e14a103]143 token_t *t = &tokens[current_token];
144 size_t end = t->char_start + t->char_length;
[a35b458]145
[5935c079]146 /*
147 * Check if the caret lies inside the token or immediately
[e14a103]148 * after it
149 */
150 if (t->char_start <= pos && pos <= end) {
151 break;
152 }
153 }
[a35b458]154
[5935c079]155 if (tokens_length == 0)
156 current_token = -1;
[a35b458]157
[5935c079]158 if ((current_token >= 0) && (tokens[current_token].type != TOKTYPE_SPACE))
[e14a103]159 *cstart = tokens[current_token].char_start;
[5935c079]160 else
[e14a103]161 *cstart = pos;
[a35b458]162
[5935c079]163 /*
164 * Extract the prefix being completed
[e14a103]165 * XXX: handle strings, etc.
166 */
[9be9c4d]167 pref_size = str_lsize(stext, pos - *cstart);
168 prefix = malloc(pref_size + 1);
169 if (prefix == NULL) {
170 retval = ENOMEM;
171 goto error;
172 }
[f737c1d5]173 prefix[pref_size] = 0;
[9be9c4d]174
[f737c1d5]175 if (current_token >= 0) {
176 str_ncpy(prefix, pref_size + 1, stext +
177 tokens[current_token].byte_start, pref_size);
178 }
[9be9c4d]179
180 /*
181 * Determine if the token being completed is a command or argument.
182 * We look at the previous token. If there is none or it is a pipe
183 * ('|'), it is a command, otherwise it is an argument.
184 */
185
186 /* Skip any whitespace before current token */
[5935c079]187 ssize_t prev_token = current_token - 1;
188 if ((prev_token >= 0) && (tokens[prev_token].type == TOKTYPE_SPACE))
[e14a103]189 prev_token--;
[a35b458]190
[9be9c4d]191 /*
192 * It is a command if it is the first token or if it immediately
193 * follows a pipe token.
194 */
[5935c079]195 if ((prev_token < 0) || (tokens[prev_token].type == TOKTYPE_SPACE))
[9be9c4d]196 cs->is_command = true;
197 else
198 cs->is_command = false;
199
200 rpath_sep = str_rchr(prefix, '/');
201 if (rpath_sep != NULL) {
202 /* Extract path. For path beginning with '/' keep the '/'. */
203 dirname = str_ndup(prefix, max(1, rpath_sep - prefix));
204 if (dirname == NULL) {
205 retval = ENOMEM;
206 goto error;
207 }
208
209 /* Extract name prefix */
210 cs->prefix = str_dup(rpath_sep + 1);
211 if (cs->prefix == NULL) {
212 retval = ENOMEM;
213 goto error;
214 }
215 *cstart += rpath_sep + 1 - prefix;
216 free(prefix);
[10ef47ba]217 prefix = NULL;
[9be9c4d]218
[3e5c48c9]219 cs->path_list = malloc(sizeof(char *) * 2);
220 if (cs->path_list == NULL) {
221 retval = ENOMEM;
222 goto error;
223 }
224 cs->path_list[0] = dirname;
225 cs->path_list[1] = NULL;
[7c3fb9b]226 /*
227 * The second const ensures that we can't assign a const
228 * string to the non-const array.
229 */
[33b8d024]230 cs->path = (const char *const *) cs->path_list;
[9be9c4d]231
232 } else if (cs->is_command) {
233 /* Command without path */
[ac2caecb]234 cs->alias_link = odict_first(&alias_dict);
[9be9c4d]235 cs->module = modules;
236 cs->builtin = builtins;
237 cs->prefix = prefix;
238 cs->path = &search_dir[0];
239 } else {
240 /* Argument without path */
241 cs->prefix = prefix;
242 cs->path = &dirlist_arg[0];
243 }
244
245 cs->prefix_len = str_length(cs->prefix);
[a35b458]246
[e14a103]247 tok_fini(&tok);
[9be9c4d]248
249 *state = cs;
250 return EOK;
251
252error:
253 /* Error cleanup */
[a35b458]254
[e14a103]255 tok_fini(&tok);
[9be9c4d]256
[3e5c48c9]257 if (cs != NULL && cs->path_list != NULL) {
258 size_t i = 0;
259 while (cs->path_list[i] != NULL) {
260 free(cs->path_list[i]);
261 ++i;
262 }
263 free(cs->path_list);
264 }
265
[10ef47ba]266 if ((cs != NULL) && (cs->prefix != NULL))
[9be9c4d]267 free(cs->prefix);
[a35b458]268
[9be9c4d]269 if (dirname != NULL)
270 free(dirname);
[a35b458]271
[9be9c4d]272 if (prefix != NULL)
273 free(prefix);
[a35b458]274
[9be9c4d]275 if (stext != NULL)
276 free(stext);
[a35b458]277
[9be9c4d]278 if (cs != NULL)
279 free(cs);
[a35b458]280
[8a6ba94]281 if (tokens != NULL)
282 free(tokens);
[9be9c4d]283
284 return retval;
285}
286
287/** Determine if completion matches the required prefix.
288 *
289 * Required prefix is stored in @a cs->prefix.
290 *
291 * @param cs Completion state object
292 * @param compl Completion string
293 * @return @c true when @a compl matches, @c false otherwise
294 */
295static bool compl_match_prefix(compl_t *cs, const char *compl)
296{
297 return str_lcmp(compl, cs->prefix, cs->prefix_len) == 0;
298}
299
300/** Get next match. */
[b7fd2a0]301static errno_t compl_get_next(void *state, char **compl)
[9be9c4d]302{
303 compl_t *cs = (compl_t *) state;
304 struct dirent *dent;
305
306 *compl = NULL;
307
308 if (cs->last_compl != NULL) {
309 free(cs->last_compl);
310 cs->last_compl = NULL;
311 }
312
[ac2caecb]313 /* Alias */
314 if (cs->alias_link != NULL) {
315 while (*compl == NULL && cs->alias_link != NULL) {
316 alias_t *data = odict_get_instance(cs->alias_link, alias_t, odict);
317 if (compl_match_prefix(cs, data->name)) {
318 asprintf(compl, "%s ", data->name);
319 cs->last_compl = *compl;
320 if (*compl == NULL)
321 return ENOMEM;
322 }
323 cs->alias_link = odict_next(cs->alias_link, &alias_dict);
324 }
325 }
326
[9be9c4d]327 /* Modules */
328 if (cs->module != NULL) {
329 while (*compl == NULL && cs->module->name != NULL) {
[ac2caecb]330 /* prevents multiple listing of an overriden cmd */
[9be9c4d]331 if (compl_match_prefix(cs, cs->module->name)) {
[ac2caecb]332 odlink_t *alias_link = odict_find_eq(&alias_dict, (void *)cs->module->name, NULL);
333 if (alias_link == NULL) {
334 asprintf(compl, "%s ", cs->module->name);
335 cs->last_compl = *compl;
336 if (*compl == NULL)
337 return ENOMEM;
338 }
[9be9c4d]339 }
340 cs->module++;
341 }
342 }
343
344 /* Builtins */
345 if (cs->builtin != NULL) {
346 while (*compl == NULL && cs->builtin->name != NULL) {
347 if (compl_match_prefix(cs, cs->builtin->name)) {
[ac2caecb]348 /* prevents multiple listing of an overriden cmd */
349 odlink_t *alias_link = odict_find_eq(&alias_dict, (void *)cs->module->name, NULL);
350 if (alias_link == NULL) {
351 asprintf(compl, "%s ", cs->builtin->name);
352 cs->last_compl = *compl;
353 if (*compl == NULL)
354 return ENOMEM;
355 }
[9be9c4d]356 }
357 cs->builtin++;
358 }
359 }
360
361 /* Files and directories. We scan entries from a set of directories. */
362 if (cs->path != NULL) {
363 while (*compl == NULL) {
364 /* Open next directory */
365 while (cs->dir == NULL) {
366 if (*cs->path == NULL)
367 break;
368
369 cs->dir = opendir(*cs->path);
[41ff85b]370
371 /* Skip directories that we fail to open. */
372 if (cs->dir == NULL)
373 cs->path++;
[9be9c4d]374 }
375
376 /* If it was the last one, we are done */
[3e5c48c9]377 if (cs->dir == NULL)
[9be9c4d]378 break;
379
380 /* Read next directory entry */
381 dent = readdir(cs->dir);
382 if (dent == NULL) {
383 /* Error. Close directory, go to next one */
384 closedir(cs->dir);
385 cs->dir = NULL;
386 cs->path++;
387 continue;
388 }
389
390 if (compl_match_prefix(cs, dent->d_name)) {
391 /* Construct pathname */
392 char *ent_path;
393 asprintf(&ent_path, "%s/%s", *cs->path, dent->d_name);
[39330200]394 vfs_stat_t ent_stat;
[23a0368]395 if (vfs_stat_path(ent_path, &ent_stat) != EOK) {
[9be9c4d]396 /* Error */
397 free(ent_path);
398 continue;
399 }
400
401 free(ent_path);
402
[ac2caecb]403 /* prevents multiple listing of an overriden cmd */
404 if (cs->is_command && !ent_stat.is_directory) {
405 odlink_t *alias_link = odict_find_eq(&alias_dict, (void *)dent->d_name, NULL);
406 if (alias_link != NULL) {
407 continue;
408 }
409 }
410
[a91c555f]411 asprintf(compl, "%s%c", dent->d_name,
412 ent_stat.is_directory ? '/' : ' ');
413 cs->last_compl = *compl;
414 if (*compl == NULL)
415 return ENOMEM;
[9be9c4d]416 }
417 }
418 }
419
420 if (*compl == NULL)
421 return ENOENT;
422
423 return EOK;
424}
425
426/** Finish completion operation. */
427static void compl_fini(void *state)
428{
429 compl_t *cs = (compl_t *) state;
430
[3e5c48c9]431 if (cs->path_list != NULL) {
432 size_t i = 0;
433 while (cs->path_list[i] != NULL) {
434 free(cs->path_list[i]);
435 ++i;
436 }
437 free(cs->path_list);
438 }
439
[9be9c4d]440 if (cs->last_compl != NULL)
441 free(cs->last_compl);
442 if (cs->dir != NULL)
443 closedir(cs->dir);
444
445 free(cs->prefix);
446 free(cs);
447}
Note: See TracBrowser for help on using the repository browser.