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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since a35b458 was a35b458, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 7 years ago

style: Remove trailing whitespace on _all_ lines, including empty ones, for particular file types.

Command used: tools/srepl '\s\+$' '' -- *.c *.h *.py *.sh *.s *.S *.ag

Currently, whitespace on empty lines is very inconsistent.
There are two basic choices: Either remove the whitespace, or keep empty lines
indented to the level of surrounding code. The former is AFAICT more common,
and also much easier to do automatically.

Alternatively, we could write script for automatic indentation, and use that
instead. However, if such a script exists, it's possible to use the indented
style locally, by having the editor apply relevant conversions on load/save,
without affecting remote repository. IMO, it makes more sense to adopt
the simpler rule.

  • 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 /* The second const ensures that we can't assign a const
222 * string to the non-const array. */
223 cs->path = (const char *const *) cs->path_list;
224
225 } else if (cs->is_command) {
226 /* Command without path */
227 cs->module = modules;
228 cs->builtin = builtins;
229 cs->prefix = prefix;
230 cs->path = &search_dir[0];
231 } else {
232 /* Argument without path */
233 cs->prefix = prefix;
234 cs->path = &dirlist_arg[0];
235 }
236
237 cs->prefix_len = str_length(cs->prefix);
238
239 tok_fini(&tok);
240
241 *state = cs;
242 return EOK;
243
244error:
245 /* Error cleanup */
246
247 tok_fini(&tok);
248
249 if (cs != NULL && cs->path_list != NULL) {
250 size_t i = 0;
251 while (cs->path_list[i] != NULL) {
252 free(cs->path_list[i]);
253 ++i;
254 }
255 free(cs->path_list);
256 }
257
258 if ((cs != NULL) && (cs->prefix != NULL))
259 free(cs->prefix);
260
261 if (dirname != NULL)
262 free(dirname);
263
264 if (prefix != NULL)
265 free(prefix);
266
267 if (stext != NULL)
268 free(stext);
269
270 if (cs != NULL)
271 free(cs);
272
273 if (tokens != NULL)
274 free(tokens);
275
276 return retval;
277}
278
279/** Determine if completion matches the required prefix.
280 *
281 * Required prefix is stored in @a cs->prefix.
282 *
283 * @param cs Completion state object
284 * @param compl Completion string
285 * @return @c true when @a compl matches, @c false otherwise
286 */
287static bool compl_match_prefix(compl_t *cs, const char *compl)
288{
289 return str_lcmp(compl, cs->prefix, cs->prefix_len) == 0;
290}
291
292/** Get next match. */
293static errno_t compl_get_next(void *state, char **compl)
294{
295 compl_t *cs = (compl_t *) state;
296 struct dirent *dent;
297
298 *compl = NULL;
299
300 if (cs->last_compl != NULL) {
301 free(cs->last_compl);
302 cs->last_compl = NULL;
303 }
304
305 /* Modules */
306 if (cs->module != NULL) {
307 while (*compl == NULL && cs->module->name != NULL) {
308 if (compl_match_prefix(cs, cs->module->name)) {
309 asprintf(compl, "%s ", cs->module->name);
310 cs->last_compl = *compl;
311 if (*compl == NULL)
312 return ENOMEM;
313 }
314 cs->module++;
315 }
316 }
317
318 /* Builtins */
319 if (cs->builtin != NULL) {
320 while (*compl == NULL && cs->builtin->name != NULL) {
321 if (compl_match_prefix(cs, cs->builtin->name)) {
322 asprintf(compl, "%s ", cs->builtin->name);
323 cs->last_compl = *compl;
324 if (*compl == NULL)
325 return ENOMEM;
326 }
327 cs->builtin++;
328 }
329 }
330
331 /* Files and directories. We scan entries from a set of directories. */
332 if (cs->path != NULL) {
333 while (*compl == NULL) {
334 /* Open next directory */
335 while (cs->dir == NULL) {
336 if (*cs->path == NULL)
337 break;
338
339 cs->dir = opendir(*cs->path);
340
341 /* Skip directories that we fail to open. */
342 if (cs->dir == NULL)
343 cs->path++;
344 }
345
346 /* If it was the last one, we are done */
347 if (cs->dir == NULL)
348 break;
349
350 /* Read next directory entry */
351 dent = readdir(cs->dir);
352 if (dent == NULL) {
353 /* Error. Close directory, go to next one */
354 closedir(cs->dir);
355 cs->dir = NULL;
356 cs->path++;
357 continue;
358 }
359
360 if (compl_match_prefix(cs, dent->d_name)) {
361 /* Construct pathname */
362 char *ent_path;
363 asprintf(&ent_path, "%s/%s", *cs->path, dent->d_name);
364 vfs_stat_t ent_stat;
365 if (vfs_stat_path(ent_path, &ent_stat) != EOK) {
366 /* Error */
367 free(ent_path);
368 continue;
369 }
370
371 free(ent_path);
372
373 /* If completing command, do not match directories. */
374 if (!ent_stat.is_directory || !cs->is_command) {
375 asprintf(compl, "%s%c", dent->d_name,
376 ent_stat.is_directory ? '/' : ' ');
377 cs->last_compl = *compl;
378 if (*compl == NULL)
379 return ENOMEM;
380 }
381 }
382 }
383 }
384
385 if (*compl == NULL)
386 return ENOENT;
387
388 return EOK;
389}
390
391/** Finish completion operation. */
392static void compl_fini(void *state)
393{
394 compl_t *cs = (compl_t *) state;
395
396 if (cs->path_list != NULL) {
397 size_t i = 0;
398 while (cs->path_list[i] != NULL) {
399 free(cs->path_list[i]);
400 ++i;
401 }
402 free(cs->path_list);
403 }
404
405 if (cs->last_compl != NULL)
406 free(cs->last_compl);
407 if (cs->dir != NULL)
408 closedir(cs->dir);
409
410 free(cs->prefix);
411 free(cs);
412}
Note: See TracBrowser for help on using the repository browser.