source: mainline/uspace/app/bdsh/compl.c@ 03b2b2c

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 03b2b2c was 5935c079, checked in by Martin Decky <martin@…>, 14 years ago

preferably use ssize_t for signed size values
cstyle

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