source: mainline/uspace/app/bdsh/compl.c@ 7c3fb9b

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

Fix block comment formatting (ccheck).

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